【Java生态圈技术总结】之深度剖析MapStruct对象拷贝工具(下)

简介: 【Java生态圈技术总结】之深度剖析MapStruct对象拷贝工具(下)

正文


2.2.6 集合属性映射


如果需要转换的Car对象中的某个属性不是基本数据类型,而是一个集合类型该怎么处理?


public class Car {
    private String brand;
    private Double price;
    private Boolean onMarket;
    private List<Person> ownerList;
    // setters + getters + toString
}

同2.2.5内容。


2.2.7 枚举映射


枚举映射的工作方式与字段映射相同。MapStruct会对具有相同名称的枚举进行映射。


public enum PayType {
    CASH,
    ALIPAY,
    WEPAY,
    DIGITAL_CASH,
    CARD_VISA,
    CARD_CREDIT;
}
public enum PayTypeNew {
    CASH,
    ALIPAY,
    WEPAY,
    DIGITAL_CASH,
    CARD_VISA,
    CARD_CREDIT;
}
@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}
@Test
public void test2(){
    PayType p1 = PayType.ALIPAY;
    PayTypeNew p2 = PayTypeMapper.INSTANCE.payTypeToPayTypeNew(p1);
    // 结果为:ALIPAY
    System.out.println("结果为:" + p2);
}

但是在更多的场景下,源枚举和目标枚举并不是一一对应的,比如目标枚举如下:

public enum PayTypeNew {
    CASH,
    NETWORK,
    CARD;
}

此时,我们就需要手动指定源枚举和目标枚举之间的对应关系:

@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);
    @ValueMappings({
            @ValueMapping(source = "ALIPAY", target = "NETWORK"),
            @ValueMapping(source = "WEPAY", target = "NETWORK"),
            @ValueMapping(source = "DIGITAL_CASH", target = "CASH"),
            @ValueMapping(source = "CARD_VISA", target = "CARD"),
            @ValueMapping(source = "CARD_CREDIT", target = "CARD")
    })
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}

如果对应CARD的场景比较多,手动一个个地对应会比较繁琐,因此还有一种方式能实现相同的效果,而且比较简洁:

@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);
    @ValueMappings({
            @ValueMapping(source = "ALIPAY", target = "NETWORK"),
            @ValueMapping(source = "WEPAY", target = "NETWORK"),
            @ValueMapping(source = "DIGITAL_CASH", target = "CASH"),
            @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "CARD")
    })
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}

MappingConstants.ANY_REMAINING表示剩下其它的源枚举和目标枚举对应不上的全部映射为指定的枚举对象。


还有一种方式,使用MappingConstants.ANY_UNMAPPED表示所有未显示指定目标枚举的都会被映射为CARD:

@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);
    @ValueMappings({
            @ValueMapping(source = "ALIPAY", target = "NETWORK"),
            @ValueMapping(source = "WEPAY", target = "NETWORK"),
            @ValueMapping(source = "DIGITAL_CASH", target = "CASH"),
            @ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = "CARD")
    })
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}


2.2.8 集合映射


如果源对象和目标对象都是集合,且对象中的属性都是基本数据类型,则映射方法和之前类似,映射接口改为如下即可:


@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    List<CarDTO> carToCarDTOList(List<Car> carList);
    Set<CarDTO> carToCarDTOSet(Set<Car> carSet);
    Map<String, CarDTO> carToCarDTOMap(Map<String, Car> carMap);
}

如果对象中属性不仅是基本数据类型,还有对象类型或对象类型的集合类型的话,mapstruct也是支持映射的,详情参考官网文档,此处不再赘述。


2.3 转换


2.3.1 类型转换


mapstruct提供了基本数据类型和包装数据类型、一些常见场景下的自动转换;


  • 基本类型及其对应的包装类型之间的转换;比如int和Integer、float和Float、long和Long、boolean和Boolean等;
  • 任意基本类型和任意包装类型之间的转换;比如int和long、byte和Integer等;
  • 任意基本类型、包转类型和String之间的转换;比如boolean和String、Integer和String;
  • 枚举和String;
  • 大数类型(BigInteger、BigDecimal)、基本类型、基本类型包装类型、String之间的相互转换;
  • 其它一些场景,参考MapStruct 1.4.2.Final Reference Guide;


2.3.2 格式转换


mapstruct可以对源对象的属性值进行格式化之后拷贝给目标对象的属性;


日期格式转换

public class Car {
    private String brand;
    private Double price;
    private LocalDate marketDate;
    // setters + getters + toString
}
public class CarDTO {
    private String brand;
    private Double price;
    private String saleDate;
    // setters + getters + toString
}
@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    @Mapping(source = "marketDate", target = "saleDate", dateFormat = "dd/MM/yyyy")
    CarDTO carToCarDTO(Car car);
}
@Test
public void test1(){
    Car wuling = new Car();
    wuling.setBrand("wuling");
    wuling.setPrice(6666.66);
    wuling.setMarketDate(LocalDate.now());
    // 转换前为:Car{brand='wuling', price=6666.66, marketDate=2022-01-19}
    System.out.println("转换前为:" + wuling);
    CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling);
    // 结果为:CarDTO{brand='wuling', price=6666.66, saleDate='19/01/2022'}
    System.out.println("结果为:" + wulingDTO);
}

数字格式转换

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    @Mapping(source = "price", target = "price", numberFormat = "$#.00")
    CarDTO carToCarDTO(Car car);
}


2.4 高级特性


2.4.1 依赖注入


在前面例子中的Mapper映射接口中,我们都需要CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);来创建一个实例,如果我们是Spring工程的话,就可以把这个实例托管给Spring进行管理。


@Mapper(componentModel = "spring")
public interface CarMapper {
    List<CarDTO> carToCarDTOList(List<Car> carList);
}

然后使用的时候,从Spring中自动注入接口对象即可:


@SpringBootTest
public class TestMapper1 {
    @Autowired
    private CarMapper carMapper;
    @Test
    public void test1(){
        Car wuling = new Car();
        wuling.setBrand("wuling");
        wuling.setPrice(6666.66);
        wuling.setMarketDate(LocalDate.now());
        Car changan = new Car();
        changan.setBrand("changan");
        changan.setPrice(7777.77);
        changan.setMarketDate(LocalDate.now());
        List<Car> carList = new ArrayList<>();
        carList.add(wuling);
        carList.add(changan);
        List<CarDTO> carDTOList = carMapper.carToCarDTOList(carList);
        System.out.println("结果为:" + carDTOList);
    }
}


2.4.2 设置默认值


常量默认值

无论源对象的属性字段值是什么,目标对象的该字段都是给定的常量值。

@Mapper(componentModel = "spring")
public interface PersonMapper {
    @Mapping(target = "name", constant = "zhangsan")
    PersonDTO personToPersonDTO(Person person);
}


空值默认值

如果源对象的属性字段值为空,那么就使用指定的默认值。

@Mapper(componentModel = "spring")
public interface PersonMapper {
    @Mapping(source = "name", target = "name", defaultValue = "unknown")
    PersonDTO personToPersonDTO(Person person);
}


2.4.3 使用表达式


@Mapper(componentModel = "spring", imports = {UUID.class, LocalDateTime.class})
public interface PersonMapper {
    @Mapping(target = "id", expression = "java(UUID.randomUUID().toString())")
    @Mapping(source = "birthdate", target = "birthdate", defaultExpression = "java(LocalDateTime.now())")
    PersonDTO personToPersonDTO(Person person);
}

或者等价写法为:

@Mapper(componentModel = "spring")
public interface PersonMapper {
    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID().toString())")
    @Mapping(source = "birthdate", target = "birthdate", defaultExpression = "java(java.time.LocalDateTime.now())")
    PersonDTO personToPersonDTO(Person person);
}

2.4.4 前置及后置方法


@Mapper(componentModel = "spring")
public abstract class PersonMapper {
    @BeforeMapping
    public void before(Person person){
        System.out.println("前置处理!!!");
        if(ObjectUtils.isEmpty(person.getName())){
            System.out.println("Person的name不能为空!");
            return;
        }
    }
    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID().toString())")
    @Mapping(source = "birthdate", target = "birthdate", defaultExpression = "java(java.time.LocalDateTime.now())")
    public abstract PersonDTO personToPersonDTO(Person person);
    @AfterMapping
    public void after(@MappingTarget PersonDTO personDTO){
        System.out.println("后置处理:" + personDTO.getName() + "!!!");
    }
}


三、参考文献


https://mapstruct.org/documentation/stable/reference/html/#basic-mappings


https://zhuanlan.zhihu.com/p/368731266


https://zhuanlan.zhihu.com/p/366178118


四、补充填坑


在正文的实例中,我们对于Bean对象都是使用手动写getter、setter、toString方法的,但是在真实开发中,大家都是采用了Lombok插件,如果不做特殊配置,就会出现mapstruct运行时lombok不生效的问题,只需要在pom配置中增加如下内容:

<annotationProcessorPaths>
  <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
   </path>                    
</annotationProcessorPaths>


相关文章
|
6小时前
|
Java
探索Java世界的奇妙工具——运算符与表达式运算符
探索Java世界的奇妙工具——运算符与表达式运算符
6 0
|
6小时前
|
安全 Java 编译器
java中类与对象回顾总结-2
java中类与对象回顾总结
15 3
|
6小时前
|
Java 编译器
java中类与对象回顾总结-1
java中类与对象回顾总结
14 3
|
6小时前
|
Java 编译器
【Java开发指南 | 第一篇】类、对象基础概念及Java特征
【Java开发指南 | 第一篇】类、对象基础概念及Java特征
9 4
|
6小时前
|
安全 Java 数据安全/隐私保护
Java一分钟之-Java反射机制:动态操作类与对象
【5月更文挑战第12天】本文介绍了Java反射机制的基本用法,包括获取Class对象、创建对象、访问字段和调用方法。同时,讨论了常见的问题和易错点,如忽略访问权限检查、未捕获异常以及性能损耗,并提供了相应的避免策略。理解反射的工作原理和合理使用有助于提升代码灵活性,但需注意其带来的安全风险和性能影响。
19 4
|
6小时前
|
Java
【JAVA基础篇教学】第五篇:Java面向对象编程:类、对象、继承、多态
【JAVA基础篇教学】第五篇:Java面向对象编程:类、对象、继承、多态
|
6小时前
|
缓存 Java 程序员
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
|
6小时前
|
Java
从源码出发:JAVA中对象的比较
从源码出发:JAVA中对象的比较
19 3
|
6小时前
|
Java
Java一分钟之-类与对象:面向对象编程入门
【5月更文挑战第8天】本文为Java面向对象编程的入门指南,介绍了类与对象的基础概念、常见问题及规避策略。文章通过代码示例展示了如何定义类,包括访问修饰符的适当使用、构造器的设计以及方法的封装。同时,讨论了对象创建与使用时可能遇到的内存泄漏、空指针异常和数据不一致等问题,并提供了相应的解决建议。学习OOP需注重理论与实践相结合,不断编写和优化代码。
28 1
|
6小时前
|
监控 Java Maven
揭秘Java Agent技术:解锁Java工具开发的新境界
作为JDK提供的关键机制,Java Agent技术不仅为Java工具的开发者提供了一个强大的框架,还为性能监控、故障诊断和动态代码修改等领域带来了革命性的变革。本文旨在全面解析Java Agent技术的应用场景以及实现方式,特别是静态加载模式和动态加载模式这两种关键模式。
38 0