【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 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
844 157
|
6月前
|
人工智能 监控 Java
Java与AI智能体:构建自主决策与工具调用的智能系统
随着AI智能体技术的快速发展,构建能够自主理解任务、制定计划并执行复杂操作的智能系统已成为新的技术前沿。本文深入探讨如何在Java生态中构建具备工具调用、记忆管理和自主决策能力的AI智能体系统。我们将完整展示从智能体架构设计、工具生态系统、记忆机制到多智能体协作的全流程,为Java开发者提供构建下一代自主智能系统的完整技术方案。
838 4
|
7月前
|
人工智能 Java API
Java AI智能体实战:使用LangChain4j构建能使用工具的AI助手
随着AI技术的发展,AI智能体(Agent)能够通过使用工具来执行复杂任务,从而大幅扩展其能力边界。本文介绍如何在Java中使用LangChain4j框架构建一个能够使用外部工具的AI智能体。我们将通过一个具体示例——一个能获取天气信息和执行数学计算的AI助手,详细讲解如何定义工具、创建智能体并处理执行流程。本文包含完整的代码示例和架构说明,帮助Java开发者快速上手AI智能体的开发。
2698 8
|
7月前
|
人工智能 缓存 监控
使用LangChain4j构建Java AI智能体:让大模型学会使用工具
AI智能体是大模型技术的重要演进方向,它使模型能够主动使用工具、与环境交互,以完成复杂任务。本文详细介绍如何在Java应用中,借助LangChain4j框架构建一个具备工具使用能力的AI智能体。我们将创建一个能够进行数学计算和实时信息查询的智能体,涵盖工具定义、智能体组装、记忆管理以及Spring Boot集成等关键步骤,并展示如何通过简单的对话界面与智能体交互。
2778 1
|
7月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
690 1
|
7月前
|
安全 Cloud Native Java
Java 模块化系统(JPMS)技术详解与实践指南
本文档全面介绍 Java 平台模块系统(JPMS)的核心概念、架构设计和实践应用。作为 Java 9 引入的最重要特性之一,JPMS 为 Java 应用程序提供了强大的模块化支持,解决了长期存在的 JAR 地狱问题,并改善了应用的安全性和可维护性。本文将深入探讨模块声明、模块路径、访问控制、服务绑定等核心机制,帮助开发者构建更加健壮和可维护的 Java 应用。
614 0
|
7月前
|
监控 Cloud Native Java
Quarkus 云原生Java框架技术详解与实践指南
本文档全面介绍 Quarkus 框架的核心概念、架构特性和实践应用。作为新一代的云原生 Java 框架,Quarkus 旨在为 OpenJDK HotSpot 和 GraalVM 量身定制,显著提升 Java 在容器化环境中的运行效率。本文将深入探讨其响应式编程模型、原生编译能力、扩展机制以及与微服务架构的深度集成,帮助开发者构建高效、轻量的云原生应用。
801 44
|
8月前
|
Java 测试技术 API
2025 年 Java 开发者必知的最新技术实操指南全览
本指南涵盖Java 21+核心实操,详解虚拟线程、Spring Boot 3.3+GraalVM、Jakarta EE 10+MicroProfile 6微服务开发,并提供现代Java开发最佳实践,助力开发者高效构建高性能应用。
1133 5
|
8月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
249 4
|
8月前
|
缓存 安全 Java
Java反射机制:动态操作类与对象
Java反射机制是运行时动态操作类与对象的强大工具,支持获取类信息、动态创建实例、调用方法、访问字段等。它在框架开发、依赖注入、动态代理等方面有广泛应用,但也存在性能开销和安全风险。本文详解反射核心API、实战案例及性能优化策略,助你掌握Java动态编程精髓。