mapstruct的使用

爱被打了一巴掌 2022-12-11 05:27 296阅读 0赞

mapstruct的使用

背景

我们可能都用过spring的BeanUtils将bean1转成bean2,例如

  1. BeancopyProperties(source, target);

这个工具其实在有些公司是被禁止的,我猜是这几个原因

说完了BeanUtils,说下mapstruct,它也是将bean1转成bean2的工具,但是它不是通过反射来进行的所以效率非常高,而且它是通过注解来实现让IDE自动产生转换代码,相当于帮你手敲了一行行的setXxx()

使用方法

  1. pom.xml 中引入(注意使用1.3.0.Final,1.4.0.Final会有点问题)

    1.3.0.Final


    org.mapstruct
    mapstruct-jdk8
    ${mapstruct.version}


    org.mapstruct
    mapstruct-processor
    ${mapstruct.version}
  2. 有这两个类

    @Data
    public class Source {

    1. private String name;

    }

  1. @Data
  2. public class Target {
  3. private String name;
  4. }
  5. // 关键的转换类,使用@Mapper注解(org.mapstruct.Mapper)
  6. @Mapper
  7. public interface Converter {
  8. // 这个INSTANCE并不是必须的,只是为了方便调用者容易使用
  9. Converter INSTANCE = Mappers.getMapper(Converter.class);
  10. // 方法名随意,没关系,具有可读语义即可
  11. Target s2t(Source source);
  12. }
  13. // 测试类
  14. public class TestDrive {
  15. public static void main(String[] args) {
  16. Source source = new Source();
  17. source.setName("name");
  18. source.setToBeIgnored("toBeIgnored");
  19. Target target = Converter.INSTANCE.s2t(source);
  20. System.out.println(target);
  21. }
  22. }

使用中的细节

常用

































































































Source Target 编译 运行 其他
有字段 name name 正常 name字段被忽略
有name字段 正常 正常 name字段被忽略,Target会保持new Target()时的name值,意思是如果Target中name保留被new时的值
name name2 正常 正常 由于字段名不同,无法将name转换至name2(特殊配置后可以
Integer age Long age 正常 正常 虽然类型不同,但是值能带过去,由于Integer->Long,属于 “小杯转大杯”,不会溢出。Long/Integer/Short/Byte/long/int/short/byte可两两互转
Long age Integer age 正常 正常 虽然类型不同,同样也能把值带过去,但是由于 “大杯转小杯”,只要源值类型超过目标类型的最大值则会溢出。Long/Integer/Short/Byte/long/int/short/byte可两两互转
Float salary Double salary 正常 正常 虽然类型不同,值能带过去,“小杯转大杯” 不溢出。同样Double可以转Float,可能会溢出。Double/Float/double/float可两两互转
Integer field Boolean field 失败 - 编译失败,类型不像Long/Integer/…或者Double/Float/…具有同类型的特性。
User user User user 正常 正常 Source和Target中相同类型且相同变量名,即使是 “非普通字段”,也是可以进行自动转换的(注1)。Source和Target中的user对象内存地址相同
List userList List userList 正常 正常 能成功转换。Source和Target的userList内存地址相同
User user UserDTO user 正常 正常 Source和Target中的类型不一样,但是变量名相同,这也是会自动推断并进行转换的。Source和Target的user的内存地址显然是不同的
List userList List userList 正常 正常 Source和Target中的类型都是List,虽然类型相同,但是mapstruct框架能够识别泛型的不同,会逐个对立面的元素转换成目标的元素。
  • 注1:普通字段是指String/Long/Integer/Short/Byte/Boolean/Double/Float以及非包装类long/int/short/byte/boolean/double/float/char)

补充

  • 如果某个字段不想被转

    1. @Mapper
    2. public interface Converter {
    3. Converter INSTANCE = Mappers.getMapper(Converter.class);
    4. @Mapping(source = "toBeIgnored", target = "toBeIgnored", ignore = true)
    5. Target s2t(Source source);
    6. }
  1. @Data
  2. public class Source {
  3. private String name;
  4. private String toBeIgnored;
  5. }
  6. @Data
  7. public class Target {
  8. private String name;
  9. private String toBeIgnored;
  10. }
  • 如果字段名不一样

    1. @Mapper
    2. public interface Converter {
    3. Converter INSTANCE = Mappers.getMapper(Converter.class);
    4. @Mapping(source = "name", target = "name2")
    5. Target s2t(Source source);
    6. }
    7. @Data
    8. public class Target {
    9. private String name;
    10. }
  1. @Data
  2. public class Source {
  3. private String name2;
  4. }
  • 如果Source和Target中的Food类的字段不同

    1. @Mapper
    2. public interface Converter {
    3. Converter INSTANCE = Mappers.getMapper(Converter.class);
    4. @Mapping(source = "food.aFoodName", target = "food.bFoodName")
    5. Target s2t(Source source);
    6. }
    7. @Data
    8. public class Source {
    9. private AFood food;
    10. }
    11. @Data
    12. public class Target {
    13. private BFood food;
    14. }
    15. @Data
    16. public class AFood {
    17. private String aFoodName;
    18. }
    19. @Data
    20. public class BFood {
    21. private String bFoodName;
    22. }
    23. //------------------------------------------
    24. // 假如source和Target中关于AFood和BFood的变量名也不同,则需要在上述的基础上加上一条新的注解
    25. @Mapping(source = "afood", target = "bfood")// 新增
    26. @Mapping(source = "afood.aFoodName", target = "bfood.bFoodName")
    27. Target s2t(Source source);
  • 如果Source和Target中存在List,而List中的元素的字段名不同

    要怎么写@Mapping呢?

    1. @Mapper
    2. public interface Converter {
    3. Converter INSTANCE = Mappers.getMapper(Converter.class);
    4. @Mapping(source = "aFoodList", target = "bFoodList")
    5. @Mapping(source = "aFoodList.aFoodName", target = "bFoodList.bFoodName")// 这个写法不行,无论换成 aFoodList.$.aFoodName,aFoodList.*.aFoodName,aFoodList[*].aFoodName,各种写法都不行
    6. Target s2t(Source source);
    7. }
    8. @Data
    9. public class Source {
    10. private List<AFood> aFoodList;
    11. }
    12. @Data
    13. public class Target {
    14. private List<BFood> bFoodList;
    15. }
    16. @Data
    17. public class AFood {
    18. private String aFoodName;
    19. }
    20. @Data
    21. public class BFood {
    22. private String bFoodName;
    23. }

    解决办法是再写一个转换方法,它自动会利用起来,例如

    1. @Mapper
    2. public interface Converter {
    3. Converter INSTANCE = Mappers.getMapper(Converter.class);
    4. @Mapping(source = "aFoodList", target = "bFoodList")
    5. Target s2t(Source source);
    6. // 增加这个后回被利用起来,用于s2t的转换
    7. @Mapping(source = "aFoodName", target = "bFoodName")
    8. BFood a2b(AFood aFood);
    9. }

如何查看生成的代码

我们使用@Mapper(org.mapstruct)时,是会自动产生一些类的,通过查看生成的类,可以知道很多的细节。
只需要command+f9就可以触发编译,以便得到最新生成的类,然后查看target/generated-sources/annotations下面对应的转换类(@Mapper修饰的类),查看它的源码就可以知道很多的细节

发表评论

表情:
评论列表 (有 0 条评论,296人围观)

还没有评论,来说两句吧...

相关阅读

    相关 使用MapStruct映射集合

    1.映射集合 通常,使用MapStruct映射集合的方式与使用简单类型的方式相同。 基本上,必须创建一个简单的接口或抽象类并声明映射方法。 根据声明,MapStruct