当前位置:   article > 正文

MapStruct进阶使用<二>_can't map property "string config" to "list<

can't map property "string config" to "list<

看了MapStruct使用入门后,相信小伙伴已经熟悉了MapStruct这款工具的初步使用。

今天接着进阶一下,来达到更深入了解和使用的目的。

我们已经知道基本类和包装类,以及String类这些,我们都不用做其他辅助操作,MapStruct就自动帮我们处理好了。

但是日常项目开发中,不可能总是简单对象属性直接的转换。有Map,List,其他对象,以及枚举类等都可能出现在我们的对象属性中,甚至你要转换的对象之间属性类型都不一样(当然不是基础类和包装类的区别哈),比如String和LocalDateTime之间的对象值copy,该如何处理?今天我们就一步步的探索,来解决这些问题。请往下看!

No1. A对象中有属性为List类型,B对象中是String[]类型

这种情况下,如果两个对象的属性名一样的话,MapStruct可以帮我们自动处理。

  1. @Data
  2. public class UserBO {
  3. private String name;
  4. private Integer id;
  5. private String address;
  6. private String email;
  7. private Integer age;
  8. private String gender;
  9. private List<String> hobbies;
  10. }
  1. @Data
  2. public class UserDO {
  3. private String name;
  4. private Integer id;
  5. private String address;
  6. private String email;
  7. private Integer age;
  8. private String gender;
  9. private String[] hobbies;
  10. }
  1. @Mapper
  2. public interface MapStructConvertUtil {
  3. MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
  4. UserDO convert(UserBO userBO);
  5. }
  1. public class MapStructTest {
  2. public static void main(String[] args) {
  3. UserBO userBO = new UserBO();
  4. userBO.setName("AwesomeJokerWang");
  5. userBO.setAddress("TJ");
  6. userBO.setAge(25);
  7. userBO.setEmail("123456789@163.com");
  8. userBO.setGender("Man");
  9. userBO.setId(1);
  10. userBO.setHobbies(new ArrayList<String>(){
  11. {
  12. add("唱");
  13. add("跳");
  14. add("Rap");
  15. }
  16. });
  17. System.out.println(userDO);
  18. }
  19. }

执行结果自然是没得问题,hobbies的值完美copy。

 看看生成后impl文件,是不是感觉有点牛批。别着急,厉害还没完呢。

  1. @Generated(
  2. value = "org.mapstruct.ap.MappingProcessor",
  3. date = "2021-06-18T18:00:46+0800",
  4. comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
  5. )
  6. public class MapStructConvertUtilImpl implements MapStructConvertUtil {
  7. @Override
  8. public UserDO convert(UserBO userBO) {
  9. if ( userBO == null ) {
  10. return null;
  11. }
  12. UserDO userDO = new UserDO();
  13. userDO.setName( userBO.getName() );
  14. userDO.setId( userBO.getId() );
  15. userDO.setAddress( userBO.getAddress() );
  16. userDO.setEmail( userBO.getEmail() );
  17. userDO.setAge( userBO.getAge() );
  18. userDO.setGender( userBO.getGender() );
  19. userDO.setHobbies( stringListToStringArray( userBO.getHobbies() ) );
  20. return userDO;
  21. }
  22. protected String[] stringListToStringArray(List<String> list) {
  23. if ( list == null ) {
  24. return null;
  25. }
  26. String[] stringTmp = new String[list.size()];
  27. int i = 0;
  28. for ( String string : list ) {
  29. stringTmp[i] = string;
  30. i++;
  31. }
  32. return stringTmp;
  33. }
  34. }

这是两个对象的属性名一致的情况下,我们甚至都不要对我们的接口做任何改动就可以达到效果。但是,如果两个对象的属性名不一样怎么办呢。这下你再厉害也找不到了哇,我们试一下。

在UserDO上做一点点改动,让它和UserBO中的hobbies属性的属性名不一样。其他都不变,我们测试一下结果。

  1. @Data
  2. public class UserDO {
  3. private String name;
  4. private Integer id;
  5. private String address;
  6. private String email;
  7. private Integer age;
  8. private String gender;
  9. private String[] d_hobbies;
  10. }

果不其然,终于为null了哇,和我们猜想的一样,而且接口的实现类中也没有了hobbies属性的赋值操作。

  1. @Generated(
  2. value = "org.mapstruct.ap.MappingProcessor",
  3. date = "2021-06-18T18:09:04+0800",
  4. comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
  5. )
  6. public class MapStructConvertUtilImpl implements MapStructConvertUtil {
  7. @Override
  8. public UserDO convert(UserBO userBO) {
  9. if ( userBO == null ) {
  10. return null;
  11. }
  12. UserDO userDO = new UserDO();
  13. userDO.setName( userBO.getName() );
  14. userDO.setId( userBO.getId() );
  15. userDO.setAddress( userBO.getAddress() );
  16. userDO.setEmail( userBO.getEmail() );
  17. userDO.setAge( userBO.getAge() );
  18. userDO.setGender( userBO.getGender() );
  19. return userDO;
  20. }
  21. }

解决这个问题,需要引出MapStruct的关键注解@Mapping。showcode。

只需要在转换接口上加入@Mapping注解,指明source的值和target的值即可做匹配,这么一来,MapStruct就可以匹配到不同名字的属性了。@Mappings是可以包裹多组@Mapping对象的。在@Mapping映射较多的时候可以用@Mappings包裹,也可以累加多个@Mapping,这个没什么影响。

  1. @Mapper
  2. public interface MapStructConvertUtil {
  3. MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
  4. @Mappings({
  5. @Mapping(source = "hobbies", target = "d_hobbies")
  6. })
  7. UserDO convert(UserBO userBO);
  8. }

看结果。hobbies的值可以正确copy,而且接口的实现类也和上一个一样。

那么,List和List,Map和HashMap等类型的复制也是一样,只要两个对象的属性名一样,就不用使用@Mapping注解来手动匹配,即使copy对象之间的属性类型是子父关系,也可以直接复制值,具体实现我就不一一列举了,有兴趣的小伙伴可以自己试试。

再来看一下第二种情况,如果上例中的B对象中hobbies的属性是String类型,不使用@Mapping,MapStruct能自动帮我们匹配么?showcode

B对象的hobbies属性类型改为String,接口去掉@Mapping注解。

  1. @Data
  2. public class UserDO {
  3. private String name;
  4. private Integer id;
  5. private String address;
  6. private String email;
  7. private Integer age;
  8. private String gender;
  9. private String hobbies;
  10. }
  1. @Mapper
  2. public interface MapStructConvertUtil {
  3. MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
  4. UserDO convert(UserBO userBO);
  5. }

测试结果:build的时候就报错了,List和String直接映射不了。

java: Can't map property "List<String> hobbies" to "String hobbies". Consider to declare/implement a mapping method: "String map(List<String> value)".

那如果把@Mapping注解加上,手动匹配后,能正确build么?可能此时有小伙伴就想到了,那A和B的对象中属性名不是一样么,你不是上面说了属性名一样的话,不用加@Mapping来手动映射么。没错,此时即使你用@Mapping注解匹配了,也还是报上面的错。因为错误的原因并不是两个对象找不到匹配,而是List和String cat not map(不能映射)。

看一下加了@Mapping的结果

  1. @Mapper
  2. public interface MapStructConvertUtil {
  3. MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
  4. @Mappings({
  5. @Mapping(source = "hobbies", target = "hobbies")
  6. })
  7. UserDO convert(UserBO userBO);
  8. }
java: Can't map property "List<String> hobbies" to "String hobbies". Consider to declare/implement a mapping method: "String map(List<String> value)".

那么,怎么解决?@Mapping注解里除了source和target,还有一个expression可以用。

  1. @Repeatable(Mappings.class)
  2. @Retention(RetentionPolicy.CLASS)
  3. @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
  4. public @interface Mapping {
  5. String target();
  6. String source() default "";
  7. String dateFormat() default "";
  8. String numberFormat() default "";
  9. String constant() default "";
  10. String expression() default "";
  11. String defaultExpression() default "";
  12. boolean ignore() default false;
  13. Class<? extends Annotation>[] qualifiedBy() default {};
  14. String[] qualifiedByName() default {};
  15. Class<?> resultType() default void.class;
  16. String[] dependsOn() default {};
  17. String defaultValue() default "";
  18. NullValueCheckStrategy nullValueCheckStrategy() default NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
  19. NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default NullValuePropertyMappingStrategy.SET_TO_NULL;
  20. Class<? extends Annotation> mappingControl() default MappingControl.class;
  21. }

我们改一下接口里的@Mapping写法,用expression方法处理List和String。这里说明一下:使用expression方法后,就不用写source了,因为表达式参数里需要写source参数,不用重复写。

  1. @Mapper
  2. public interface MapStructConvertUtil {
  3. MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
  4. @Mappings({
  5. @Mapping(target = "hobbies", expression = "java(userBO.getHobbies().get(0))")
  6. })
  7. UserDO convert(UserBO userBO);
  8. }

再测试看一下结果。成功赋值,我们用userBO.getHobbies().get(arg)来获取需要匹配的参数

其实,介绍到这,小伙伴们也基本也就知道了,对象属性类型或名字不一样时,用@Mapping注解处理就可以,遇到从集合中取某一个值和单个值匹配时,用expression表达式处理就行。expression的写法也很简单,Java方法里写你的逻辑处理就行,就跟写Java代码一样。

expression = “java(处理逻辑)” 

后面再简单看一下对象中有枚举,以及String和LocalDateTime之间的代码示例。因为核心的处理就是expression的使用

No2. A对象中有属性为Enum类型,B对象中是基本类型或包装类型

  1. @Data
  2. public class UserBO {
  3. private String name;
  4. private Integer id;
  5. private String address;
  6. private String email;
  7. private Integer age;
  8. private String gender;
  9. private List<String> hobbies;
  10. private SkillEnums role;
  11. }
  1. @Data
  2. public class UserDO {
  3. private String name;
  4. private Integer id;
  5. private String address;
  6. private String email;
  7. private Integer age;
  8. private String gender;
  9. private String hobbies;
  10. private String d_role;
  11. }

SkillEnums对象

  1. @Getter
  2. @AllArgsConstructor
  3. public enum SkillEnums {
  4. JAVA("java","25年了"),
  5. PHP("php", "号称全世界最好的语言"),
  6. C_Sharp("c#", "微软小宝贝儿"),
  7. C("c","计算机原理");
  8. private final String code;
  9. private final String msg;
  10. public static String getSkill(String code){
  11. SkillEnums[] values = SkillEnums.values();
  12. for (int i = 0; i < values.length; i++) {
  13. if (code.equals(values[i].getCode())) {
  14. return values[i].getCode();
  15. }
  16. }
  17. return "";
  18. }
  19. }

对象名一样的话不用@Mapping,不一样的话加个@Mapping手动做匹配,经测试可以成功赋值。

我们主要看一MapStruct帮我们生成的接口实现类。不得不说MapStruct太厉害了。

  1. @Generated(
  2. value = "org.mapstruct.ap.MappingProcessor",
  3. date = "2021-06-19T23:33:43+0800",
  4. comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
  5. )
  6. public class MapStructConvertUtilImpl implements MapStructConvertUtil {
  7. @Override
  8. public UserDO convert(UserBO userBO) {
  9. if ( userBO == null ) {
  10. return null;
  11. }
  12. UserDO userDO = new UserDO();
  13. userDO.setName( userBO.getName() );
  14. userDO.setId( userBO.getId() );
  15. userDO.setAddress( userBO.getAddress() );
  16. userDO.setEmail( userBO.getEmail() );
  17. userDO.setAge( userBO.getAge() );
  18. userDO.setGender( userBO.getGender() );
  19. if ( userBO.getRole() != null ) {
  20. userDO.setRole( userBO.getRole().name() );
  21. }
  22. userDO.setHobbies( userBO.getHobbies().get(0) );
  23. return userDO;
  24. }
  25. }

No3. A对象中有属性为String类型,B对象中是LocalDate或LocalDateTime等类型

  1. @Data
  2. public class UserBO {
  3. private String name;
  4. private Integer id;
  5. private String address;
  6. private String email;
  7. private Integer age;
  8. private String gender;
  9. private List<String> hobbies;
  10. private SkillEnums role;
  11. private String birthday;
  12. }
  1. @Data
  2. public class UserDO {
  3. private String name;
  4. private Integer id;
  5. private String address;
  6. private String email;
  7. private Integer age;
  8. private String gender;
  9. private String hobbies;
  10. private String role;
  11. private LocalDate birthday;
  12. }

修改一下接口,增加expression表达式处理String和LocalDate的转换。DateUtils.convertDate是我写的一个工具类。

  1. @Mapper
  2. public interface MapStructConvertUtil {
  3. MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
  4. @Mappings({
  5. @Mapping(target = "hobbies", expression = "java(userBO.getHobbies().get(0))"),
  6. @Mapping(target = "birthday", expression = "java(DateUtils.convertDate(userBO.getBirthday()))")
  7. })
  8. UserDO convert(UserBO userBO);
  9. }

DateUtils.class 

  1. public class DateUtils {
  2. public static LocalDate convertDate(String strDate) {
  3. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  4. return LocalDate.parse(strDate, formatter);
  5. }
  6. }

OK,介绍到这,我想小伙伴就已经对MapStruct的使用有了更进一步的了解。

不得不说MapStruct这个小工具很强大,很简单,也很好用。后续有想要深入了解的小伙伴可以看看源码,核心的Mappers方法实现以及其他的注解。

好了,MapStruct的使用基本就介绍完了,项目中可以考虑使用这款强大而工具来实现对象之间的转换,而不用再写那么多繁琐的set转换了。

最后,如果觉得写的对您有用,希望您留下您的素质三连,非常感谢。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/261867
推荐阅读
相关标签
  

闽ICP备14008679号