MapStruct进阶使用<二>

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

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

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

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

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

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

@Data
public class UserBO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    private List<String> hobbies;
}
@Data
public class UserDO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    private String[] hobbies;
}
@Mapper
public interface MapStructConvertUtil {
    MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
    UserDO convert(UserBO userBO);
}
public class MapStructTest {
    public static void main(String[] args) {
        UserBO userBO = new UserBO();
        userBO.setName("AwesomeJokerWang");
        userBO.setAddress("TJ");
        userBO.setAge(25);
        userBO.setEmail("123456789@163.com");
        userBO.setGender("Man");
        userBO.setId(1);
        userBO.setHobbies(new ArrayList<String>(){
            {
                add("唱");
                add("跳");
                add("Rap");
            }
        });
        System.out.println(userDO);
    }
}

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

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

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2021-06-18T18:00:46+0800",
    comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
)
public class MapStructConvertUtilImpl implements MapStructConvertUtil {

    @Override
    public UserDO convert(UserBO userBO) {
        if ( userBO == null ) {
            return null;
        }

        UserDO userDO = new UserDO();

        userDO.setName( userBO.getName() );
        userDO.setId( userBO.getId() );
        userDO.setAddress( userBO.getAddress() );
        userDO.setEmail( userBO.getEmail() );
        userDO.setAge( userBO.getAge() );
        userDO.setGender( userBO.getGender() );
        userDO.setHobbies( stringListToStringArray( userBO.getHobbies() ) );

        return userDO;
    }

    protected String[] stringListToStringArray(List<String> list) {
        if ( list == null ) {
            return null;
        }

        String[] stringTmp = new String[list.size()];
        int i = 0;
        for ( String string : list ) {
            stringTmp[i] = string;
            i++;
        }

        return stringTmp;
    }
}

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

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

@Data
public class UserDO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    private String[] d_hobbies;
}

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

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2021-06-18T18:09:04+0800",
    comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
)
public class MapStructConvertUtilImpl implements MapStructConvertUtil {

    @Override
    public UserDO convert(UserBO userBO) {
        if ( userBO == null ) {
            return null;
        }

        UserDO userDO = new UserDO();

        userDO.setName( userBO.getName() );
        userDO.setId( userBO.getId() );
        userDO.setAddress( userBO.getAddress() );
        userDO.setEmail( userBO.getEmail() );
        userDO.setAge( userBO.getAge() );
        userDO.setGender( userBO.getGender() );

        return userDO;
    }
}

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

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

@Mapper
public interface MapStructConvertUtil {
    MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);

    @Mappings({
            @Mapping(source = "hobbies", target = "d_hobbies")
    })
    UserDO convert(UserBO userBO);
}

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

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

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

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

@Data
public class UserDO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    private String hobbies;
}
@Mapper
public interface MapStructConvertUtil {
    MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
    UserDO convert(UserBO userBO);
}

测试结果: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的结果

@Mapper
public interface MapStructConvertUtil {
    MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);

    @Mappings({
            @Mapping(source = "hobbies", target = "hobbies")
    })
    UserDO convert(UserBO userBO);
}
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可以用。

@Repeatable(Mappings.class)
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Mapping {
    String target();

    String source() default "";

    String dateFormat() default "";

    String numberFormat() default "";

    String constant() default "";

    String expression() default "";

    String defaultExpression() default "";

    boolean ignore() default false;

    Class<? extends Annotation>[] qualifiedBy() default {};

    String[] qualifiedByName() default {};

    Class<?> resultType() default void.class;

    String[] dependsOn() default {};

    String defaultValue() default "";

    NullValueCheckStrategy nullValueCheckStrategy() default NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;

    NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default NullValuePropertyMappingStrategy.SET_TO_NULL;

    Class<? extends Annotation> mappingControl() default MappingControl.class;
}

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

@Mapper
public interface MapStructConvertUtil {
    MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);

    @Mappings({
            @Mapping(target = "hobbies", expression = "java(userBO.getHobbies().get(0))")
    })
    UserDO convert(UserBO userBO);
}

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

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

expression = “java(处理逻辑)” 

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

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

@Data
public class UserBO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    private List<String> hobbies;
    private SkillEnums role;
}
@Data
public class UserDO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    private String hobbies;
    private String d_role;
}

SkillEnums对象

@Getter
@AllArgsConstructor
public enum SkillEnums {
    JAVA("java","25年了"),
    PHP("php", "号称全世界最好的语言"),
    C_Sharp("c#", "微软小宝贝儿"),
    C("c","计算机原理");
    private final String code;
    private final String msg;
    
    public static String getSkill(String code){
        SkillEnums[] values = SkillEnums.values();
        for (int i = 0; i < values.length; i++) {
            if (code.equals(values[i].getCode())) {
                return values[i].getCode();
            }
        }
        return "";
    }
}

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

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

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2021-06-19T23:33:43+0800",
    comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
)
public class MapStructConvertUtilImpl implements MapStructConvertUtil {

    @Override
    public UserDO convert(UserBO userBO) {
        if ( userBO == null ) {
            return null;
        }

        UserDO userDO = new UserDO();

        userDO.setName( userBO.getName() );
        userDO.setId( userBO.getId() );
        userDO.setAddress( userBO.getAddress() );
        userDO.setEmail( userBO.getEmail() );
        userDO.setAge( userBO.getAge() );
        userDO.setGender( userBO.getGender() );
        if ( userBO.getRole() != null ) {
            userDO.setRole( userBO.getRole().name() );
        }

        userDO.setHobbies( userBO.getHobbies().get(0) );

        return userDO;
    }
}

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

@Data
public class UserBO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    private List<String> hobbies;
    private SkillEnums role;
    private String birthday;
}
@Data
public class UserDO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    private String hobbies;
    private String role;
    private LocalDate birthday;
}

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

@Mapper
public interface MapStructConvertUtil {
    MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);

    @Mappings({
            @Mapping(target = "hobbies", expression = "java(userBO.getHobbies().get(0))"),
            @Mapping(target = "birthday", expression = "java(DateUtils.convertDate(userBO.getBirthday()))")
    })
    UserDO convert(UserBO userBO);
}

DateUtils.class 

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

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

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

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

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

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值