MapStruct 运用之争

     背景

        最近在欣赏其他项目的代码时,偶然发现一个MapStruct 的使用案例,一见之下,颇为欣喜。回头再看看我们团队那群卧龙凤雏写的代码,感觉简直是在玷污我的眼睛,我决定在我们团队推广使用MapStruct,而在做出这个决定之前,我还有一项无比艰巨的任务——说服团队的胡大佬。于是乎,就有了此次的华山论 MapStruct 之争。

     MapStruct介绍

        MapStruct 是一个用于 Java bean 映射的代码生成器。它提供了一个注解处理器,可以根据接口定义自动生成 Java bean 映射代码。它能够自动生成类型转换器,从而使得在不同类型之间进行转换变得简单,而无需程序员编写冗长、重复的映射代码。它支持自定义类型转换器,可以自动生成类型转换代码,从而减少手动编写代码的工作量。
MapStruct 的使用非常简单,只需要定义一个接口,然后使用注解指定需要转换的类型和属性。然后,MapStruct 会生成一个实现该接口的类,该类将自动执行类型转换。

       现如今,分层结构的服务架构(如领域驱动模型)大行其道,为了实现各层之间的解耦,对象之间的相互转换变得格外频繁,时势造英雄,这也成了MapStruct大显身友的好机会,相比于其他的对象转换方法,MapStruct具有以下特点:

       1、安全性高,在编译期就实现源对象到目标对象的映射,不会把隐患留到运行期。
       2、速度快,在运行期间直接调用实现类的方法,不需要使用反射进行转化,提升了转换效率。

       下面,我们用一个小案例来领略MapStruct的魅力。

     MapStruct实践

       1、引入maven依赖

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.5.5.Final</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.5.5.Final</version>
        </dependency>

        2、整理对象类UserEntity(实体类) 和 UserResp(返回类),注意,两个类的属性大致相同,但有部分属性不同,比如birthDay,一个是Date型,一个是String型,而对于部分字段如sex,实体类中存的是“0”,“1”,而在返回类中,更希望显示成“男“,”女“,这是很普遍的需求。

// 实体类
@Data
public class UserEntity {

    /**
     * 主键ID
     */
    private String id;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 性别
     */
    private String sex;

    /**
     * 生日
     */
    private Date birthDay;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 数据状态
     */
    private String status;

    /**
     * 静态数据
     */
    private String contentDesc;

    /**
     * 需要忽略的数据
     */
    private String ignoreDesc;

    /**
     * entity独有数据
     */
    private String entityDesc;
}


// 返回类
@Data
public class UserResp {

    /**
     * 主键ID
     */
    private String id;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 性别
     */
    private String sex;

    /**
     * 生日
     */
    private String birthDay;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 数据状态
     */
    private String status;

    /**
     * 静态数据
     */
    private String contentDesc;

    /**
     * 忽略数据
     */
    private String ignoreDesc;

    /**
     * resp独有数据
     */
    private String respDesc;
}

         3、编写转换类

//对象转换接口
@Mapper
public interface UserConverter {
    
    UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
    
    List<UserResp> toRespList(List<UserEntity> list);
    
    
    // 用SexEnum.getNameByValue()方法对UserEntity里的sex字段进行转换
    @Mapping(target = "sex", expression = "java(com.leixi.converter.pojo.enums.SexEnum.getNameByValue(entity.getSex()))")
    // 用DateUtil.formatDate() 对UserEntity里的birthDay字段进行转换
    @Mapping(target = "birthDay", expression = "java(com.leixi.converter.util.DateUtil.formatDate(entity.getBirthDay()))")
    // 用getStatusBySexAndAge(String sex, Integer) 方法计算status,存入UserResp中
    @Mapping(target = "status", expression = "java(getStatusBySexAndAge(entity.getSex(), entity.getAge()))")
    // UserEntity里的字段entityDesc赋值给UserResp里的respDesc 
    @Mapping(target = "respDesc", source = "entityDesc")
    // UserResp里的contentDesc设为静态值ConverterConstant.RESP_DESC
    @Mapping(target = "contentDesc", constant = ConverterConstant.RESP_DESC)
    // 不对UserEntity中的ignoreDesc字段进行转换
    @Mapping(target = "ignoreDesc", source = "ignoreDesc", ignore = true)
    UserResp toResp(UserEntity entity);
    
    default String getStatusBySexAndAge(String sex, Integer age) {
        if (Objects.equals(SexEnum.Male.getValue(), sex) && age >= 18) {
            return "成年男性";
        } else if (Objects.equals(SexEnum.Female.getValue(), sex) && age >= 18){
            return "成年女性";
        } else if (Objects.equals(SexEnum.Male.getValue(), sex)) {
            return "幼年男性";
        } else {
            return "幼年女性";
        }
    }
    
    List<UserEntity> toEntityList(List<UserResp> list);

    @Mapping(target = "sex", expression = "java(com.leixi.converter.pojo.enums.SexEnum.getValueByName(resp.getSex()))")
    @Mapping(target = "birthDay", expression = "java(com.leixi.converter.util.DateUtil.parseDate(resp.getBirthDay()))")
    @Mapping(target = "contentDesc", constant = ConverterConstant.ENTITY_DESC)
    @Mapping(target = "ignoreDesc", source = "ignoreDesc", ignore = true)
    @Mapping(target = "entityDesc", source = "respDesc")
    UserEntity toEntity(UserResp resp);
}


//性别枚举
public enum SexEnum {

    Male("1", "男"),
    Female("0", "女"),
    Unknown("-1", "未知"),
    ;

    private final String value;
    public String getValue() {
        return value;
    }

    public String getName() {
        return name;
    }

    private final String name;
    
    SexEnum(String value, String name) {
        this.value = value;
        this.name = name;
    }
    public static String getNameByValue(String value) {
        for (SexEnum en : SexEnum.values()) {
            if (value.equals(en.value)) {
                return en.getName();
            }
        }
        return SexEnum.Unknown.getName();
    }

    public static String getValueByName(String name) {
        for (SexEnum en : SexEnum.values()) {
            if (name.equals(en.name)) {
                return en.getValue();
            }
        }
        return SexEnum.Unknown.getValue();
    }
}


//时间转换工具类
public class DateUtil {

    public static final String DATE_YYYY_MM_DD = "yyyy-MM-dd";

    public static String formatDate(Date date) {
        if (Objects.isNull(date)) {
            return "";
        }
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_YYYY_MM_DD);
            return simpleDateFormat.format(date);
        } catch (Exception e) {
            return "";
        }
    }

    public static Date parseDate(String date) {
        if (StringUtils.isEmpty(date)) {
            return null;
        }
        SimpleDateFormat sdf = new SimpleDateFormat(DATE_YYYY_MM_DD);
        try {
            return sdf.parse(date);
        } catch (ParseException e) {
            return new Date();
        }
    }
}


          4、编写Controller

@RestController
public class DemoController {
    
    @PostMapping("/getUserEntity")
    public Object getUserEntity(@RequestBody UserResp user) {
        return UserConverter.INSTANCE.toEntity(user);
    }

    @PostMapping("/getUserResp")
    public Object getUserResp(@RequestBody UserEntity user) {
        user.setBirthDay(new Date());
        return UserConverter.INSTANCE.toResp(user);
    }
}

         5、编译代码后,可以看到Target里生成了对象转换的实体方法,如下

          6、执行postman进行测试,效果杠杠滴。

      MapStruct正名之争

       (后面这部分都是没营养的口水战,不看也罢!)

        整理好测试用例后,我志得意满的找到胡大佬,盛气凌人的说:”嘞个……大佬,请教下你平常是用什么方式实现两个对象之间的转换的?”

        胡大佬:“这种事还用问我?手写呗,能费你多少功夫?”

        雷袭:哦,你看是不是这样……

    public UserResp transEntityToResp(UserEntity entity) {
        UserResp userResp = new UserResp();
        userResp.setRespDesc(entity.getEntityDesc());
        userResp.setId(entity.getId());
        userResp.setUserName(entity.getUserName());
        userResp.setAge(entity.getAge());
        userResp.setName1(entity.getName1());
        userResp.setName2(entity.getName2());
        userResp.setName3(entity.getName3());
        userResp.setName4(entity.getName4());
        userResp.setName5(entity.getName5());
        userResp.setName6(entity.getName6());
        userResp.setName7(entity.getName7());
        userResp.setName8(entity.getName8());
        userResp.setName9(entity.getName9());
        userResp.setName10(entity.getName10());
        userResp.setName11(entity.getName11());
        userResp.setName12(entity.getName12());
        userResp.setName13(entity.getName13());
        userResp.setName14(entity.getName14());
        userResp.setName15(entity.getName15());
        userResp.setName16(entity.getName16());
        userResp.setName17(entity.getName17());
        userResp.setName18(entity.getName18());
        userResp.setName19(entity.getName19());
        userResp.setName20(entity.getName20());
        userResp.setName21(entity.getName21());
        userResp.setName22(entity.getName22());
        userResp.setName23(entity.getName23());
        userResp.setName24(entity.getName24());
        userResp.setName25(entity.getName25());
        userResp.setName26(entity.getName26());
        userResp.setName27(entity.getName27());
        userResp.setName28(entity.getName28());
        userResp.setName29(entity.getName29());
        userResp.setName30(entity.getName30());
        userResp.setName31(entity.getName31());
        userResp.setName32(entity.getName32());
        userResp.setName33(entity.getName33());
        return userResp;
    }

        雷袭:“再提一嘴哈,我还有个类八十多个属性,都这么写的话有点费头发啊!”

        胡大佬: “这……确实会费点功夫哈!你也没说属性这么多啊!这种情况,我一般是用反射的,几行代码就能搞定。”

        雷袭:“哦还能这么干啊!”

        胡大佬:“这不废话嘛,办法总比问题多,你平常还是要多动脑子!”

        雷袭:“那大佬你帮忙看看,我这个用反射咋报错了呢?”

        胡大佬:“……你这个同名属性类型不同肯定不能用反射啊,你刚也没说有这种情况啊!”

        雷袭:“废话,你又不是第一天当程序员,这种情况都想不到?”

        胡大佬:”那,那我用BeanUtils.copyProperties()总行了吧,再对这种字段进行特殊处理?”

        雷袭:“可是BeanUtils效率差啊,而且遇到Integer转成int的情况也会在执行时报错。现在正经人谁用copyProperties啊!”

        胡大佬(怒):“这也不行,那也不行,你这不是在针对我吗?”

        雷袭:“没没没,我可没有针对你的意思,我就是单纯的觉得你说的不对!”

        胡大佬:“那你行你上啊,你能有啥好办法?”

        雷袭(拿出准备好的代码):“大佬你看这样实现行不行?”

        胡大佬(神情中有着掩饰不住的震惊):“MapStruct?我从来不用这种东西,我们程序员不能太依赖工具,程序员唯一能依仗的就是自己高超的技术!”

        雷袭:“可是人与动物最大的区别不就是人擅长使用工具吗?”

        胡大佬:“那……那你这个工具也不咋行啊!这么多方法都写在注解里,我要是变更了方法维护起来多不方便?要是没同步,一编译不就报错了!”

        雷袭:“这也正是它的优点啊,在编译的时候就能发现问题,总比留坑到执行时再暴露要好吧!”

        胡大佬(犹豫):“道理是这个道理,可是我还是觉得你在针对我!”

        雷袭:“呵呵呵,您看人真准!”

        胡大佬(冷笑):“行啊,胆儿肥了是吧,敢跟我杠上,有种下班了别走!”

        雷袭:“你还威胁我,你你你信不信我告诉总监去!”

        总监:“行了,你俩都别吵了,我这里有两颗果果,你们一人一个,吃完了都给我搬砖去。谁要是不听话,下次可就没好果子吃了!”

  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值