# DTO转VO工具

作者:一瓶牛奶 (opens new window)编程导航星球 (opens new window) 编号 13461

data工具,实现了对象拷贝 DTO -> VO 只需要实现一个类即可

# data-utils

data工具,实现了对象拷贝 DTO -> VO

# 解决的问题

Mapstruct需要安插件!!!!很多云桌面等会很不方便

org.springframework.beans.BeanUtils有一个 copyProperties的方法

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AccountDTO  {

    private Long id;
    private String username;
    private String password;
    private String gender;
    private String email;
    private String role;
    private Date registerTime;
    private Integer isDelete;
}
@Data
public class AccountVO {
    private String username;
    private String gender;
    private String role;
    //男 1 女 0
    private String genderNum;

    private Integer isDelete;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

比如我们有两个类 DTOVO 在给前端VO的时候,需要把DTO 转换成VO 我们需要这样写 遇到genderNum DTO类里没有的这种情况,我们需要手动set的时候,是下面这种写法

import com.example.entity.AccountVO;

@Data
public class DataTest {
    @Test
    void contextLoads1() {
        AccountDTO accountDTO = new AccountDTO(1L, "test", "123456", "男", "112@qq.com", "user", new Date(), 1);
        AccountVO accountVO =  new AccountVO();
        BeanUtils.copyProperties(accountDTO, accountVO);
        accountVO.setGender(Objects.equals(accountDTO.getGender(), "男") ? "1" : "0");
        System.out.println(accountVO);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

有两个缺点:

  1. VO需要手动new一个出来
  2. 如果VO里面有很多字段需要手动set的时候,会把代码写的很长
  3. 如果遇到集合里面想要转换, sonar还会报不让在循环中创建对象的问题

# BaseData的作用

BaseData代码(项目里只要有这个就可以)

import com.example.common.Constants;
import com.example.common.ErrorCode;
import com.example.exception.BusinessException;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.*;
import java.util.Arrays;
import java.util.function.Consumer;

public interface BaseData {

    /**
     * 默认方法,根据传入的Class3类型将当前对象转换为目标对象并执行操作
     *
     * @param clazz    目标类
     * @param consumer 可以写lambda表达式比如
     *  accountDTO.asTargetObject(AccountVO.class,v->{
     *      v.setGenderNum(Objects.equals(accountDT0.getGender(),"男")?"1":"0");
     *          });
     *  consumer是这段
     *  v->{
     *      v.setGenderNum(Objects.equals(accountDT0.getGender(),"男")?"1":"0");
     *  }
     */
    default <V> V asTargetObject(Class<V> clazz, Consumer<V> consumer) {
//        调用 asTargetObject 方法将当前对象转换为目标对象
        V v = this.asTargetObject(clazz);
//        执行传入的Consumer操作
        consumer.accept(v);
        return v;
    }

    /**
     * 默认方法 将当前对象转换为目标对象
     *
     * @param clazz 目标类
     * @param <V>   目标类类型 如AccountVO
     * @return 转换完的目标类
     */
    default <V> V asTargetObject(Class<V> clazz) {
        try {
//            获取目标类的所有字段
            Field[] declaredFields = clazz.getDeclaredFields();
//            获取目标类的构造函数
            Constructor<V> constructor = clazz.getConstructor();
//            根据构造函数实例化目标对象
            V v = constructor.newInstance();
//            遍历目标类的每个字段,并进行转换试值
            Arrays.stream(declaredFields).forEach(declaredField -> convert(declaredField, v));
            return v;
        } catch (ReflectiveOperationException e) {
//            //捕获ReflectiveOperationException异常,抛出自定义的BusinessException
            throw new BusinessException(ErrorCode.CAST_OBJECT_ERROR);
        }

    }

    /**
     * 默认方法,将字段转换并赋值给目标对象
     * @param field VO剩余的字段,自定义
     * @param vo    要转换的VO
     */
    default void convert(Field field, Object vo) {

        try {
//            获取当前对象中与目标字段同名的字段
            Field source = this.getClass().getDeclaredField(field.getName());
//            设置字段可访问
            ReflectionUtils.makeAccessible(field);
            ReflectionUtils.makeAccessible(source);
//            获取当前对象中获取字段值的方法和目标对象中设置字段值的方法,并进行转换赋值
            Method sourceGetter = this.getClass().getMethod(Constants.GET + capitalize(field.getName()));
            Method targetSetter = vo.getClass().getMethod(Constants.SET + capitalize(field.getName()), field.getType());
            Object value = sourceGetter.invoke(this);
            targetSetter.invoke(vo, value);
        } catch (NoSuchFieldException | InvocationTargetException | IllegalAccessException |
                 NoSuchMethodException ignored) {
//              这里ignored 原因是
//              两个类的字段数量不一样的时候,会报 java.lang.NoSuchFieldException
//              但是多出来的字段我们是可以处理的
        }
    }

    /**
     * 默认方法,将字符串首字母大写
     * @param str   比如字段名 name
     * @return 返回 Name
     */
    default String capitalize(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return Character.toUpperCase(str.charAt(0)) + str.substring(1);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
  1. 实现对象深拷贝
@Data
public class DataTest {
    @Test
    void contextLoads1() {
        AccountDTO accountDTO = new AccountDTO(1L,"test","123456","男","112@qq.com","user",new Date(),1);
        AccountVO accountVO = accountDTO.asTargetObject(AccountVO.class,v->{
            v.setGenderNum(Objects.equals(accountDTO.getGender(), "男") ? "1" : "0");
        });
        System.out.println(accountVO);
    }
}
1
2
3
4
5
6
7
8
9
10
11
  1. 实现对象Collection 深拷贝(List Set ...)
@Data
public class DataTest {
    @Test
    void contextLoads2() {
        AccountDTO accountDTO = new AccountDTO(1L,"test","123456","男","112@qq.com","user",new Date(),0);
        AccountDTO accountDTO2 = new AccountDTO(2L,"test2","123456","女","112@qq.com","admin",new Date(),1);
        List<AccountDTO> accountDTOList = new ArrayList<>();
        accountDTOList.add(accountDTO);
        accountDTOList.add(accountDTO2);
        List<AccountVO> list = accountDTOList.stream().map(source -> source.asTargetObject(AccountVO.class, v-> {
            v.setGenderNum(Objects.equals(source.getGender(), "男") ? "1" : "0");
        })).collect(Collectors.toList());
        list.forEach(System.out::println);
    }

    @Test
    void contextLoads3() {
        AccountDTO accountDTO = new AccountDTO(1L,"test","123456","男","112@qq.com","user",new Date(),1);
        AccountDTO accountDTO2 = new AccountDTO(2L,"test2","123456","女","112@qq.com","admin",new Date(),0);
        Set<AccountDTO> accountDTOSet = new HashSet<>();
        accountDTOSet.add(accountDTO);
        accountDTOSet.add(accountDTO2);
        Set<AccountVO> set = accountDTOSet.stream().map(source -> source.asTargetObject(AccountVO.class, v-> {
            v.setGenderNum(Objects.equals(source.getGender(), "男") ? "1" : "0");
        })).collect(Collectors.toSet());
        set.forEach(System.out::println);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 实现步骤

1. dto 实现 BaseData接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AccountDTO implements BaseData {

    private Long id;
    private String username;
    private String password;
    private String gender;
    private String email;
    private String role;
    private Date registerTime;
    private Integer isDelete;
}
2. dto.asViewObject(Target.class);
3. 如果 Target 还有其他字段 也可以自定义,例如测试用例中的genderNum(只是简单举的例子,按照项目实际来)
4. `isDelete` 这种is开头的也支持
   1. 如果有问题,看下lombok版本是否有问题 此项目用的版本是 1.18.28 没问题
   2. 如果没用lombok 手动加上getIsDelete() 用这个格式就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 注意

两个类 相同的字段名的字段类型 必须完全一样!!!

最近更新: 3/31/2024, 6:26:50 PM
编程导航   |