【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>


相关文章
|
27天前
|
监控 Java 测试技术
Java开发现在比较缺少什么工具?
【10月更文挑战第15天】Java开发现在比较缺少什么工具?
34 1
|
17天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
21天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
42 17
|
11天前
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
19天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
19天前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
|
20天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
22天前
|
Web App开发 Java
使用java操作浏览器的工具selenium-java和webdriver下载地址
【10月更文挑战第12天】Selenium-java依赖包用于自动化Web测试,版本为3.141.59。ChromeDriver和EdgeDriver分别用于控制Chrome和Edge浏览器,需确保版本与浏览器匹配。示例代码展示了如何使用Selenium-java模拟登录CSDN,包括设置驱动路径、添加Cookies和获取页面源码。
|
20天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
18 0
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。