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

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

正文


一、常用的对象拷贝工具基本介绍


属性拷贝工具有很多,也许你用过如下的一些:

  • Apache commons-beanutils
  • Spring BeanUtils
  • cglib BeanCopier
  • HuTool BeanUtils
  • MapStruct
  • getter & setter


这些属性拷贝工具各自有什么特点和区别?在日常开发使用中,我们该如何做出选择?


1.1 Apache BeanUtils


参数顺序和其它的工具正好相反,导致使用不顺手,容易产生问题;

阿里巴巴代码扫描插件会给出明确的告警;

基于反射实现,性能较差;

不推荐使用;


1.2 Spring BeanUtils


基于内省+反射,借助getter/setter方法实现属性拷贝,性能比apache高;

在简单的属性拷贝场景下推荐使用;


1.3 cglib BeanCopier


通过动态代理的方式来实现属性拷贝;

性能高效;

在简单的属性拷贝场景下推荐使用;


1.4 HuTool BeanUtils


性能介于apache和Spring之间;

需要额外引入HuTool的依赖;


1.5 MapStruct


基于getter/setter方法实现属性拷贝,在编译时自动生成实现类的代码;

性能媲美getter & setter;

强大的功能可以实现深度拷贝;

缺点是需要声明bean的转换接口类;


1.6 getter & setter


性能最高,但是需要手动拷贝;


1.7 总结


经过第三方的对比结果,总的下来,推荐使用顺序为:

apache < HuTool < Spring < cglib < Mapstruct


二、使用介绍


2.1 准备工作


<!-- 导入MapStruct的核心注释 -->
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<!-- MapStruct在编译时工作,并且会集成到像Maven和Gradle这样的构建工具上,我们还必须在<build中/>标签中添加一个插件maven-compiler-plugin,并在其配置中添加annotationProcessorPaths,该插件会在构建时生成对应的代码。 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>


2.2 映射


2.2.1 基本映射


我们现在需要实现一个场景,Car是一个domain层的对象实例,在从数据库读取出来后传递给service层需要转换为CarDTO,这两个实例的所有属性全部相同,现在需要使用mapstruct来完成这个目标。


public class Car {
    private String brand;
    private Double price;
    private Boolean onMarket;
    ...
    // setters + getters + toString
}
public class CarDTO {
    private String brand;
    private Double price;
    private Boolean onMarket;
    ...
    // setters + getters + toString
}

我们需要新建一个Mapper接口,来映射这两个对象之间的属性。

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    CarDTO carToCarDTO(Car car);
}

然后就可以进行测试了:

@Test
public void test1(){
    Car wuling = new Car();
    wuling.setBrand("wuling");
    wuling.setPrice(6666.66);
    wuling.setOnMarket(true);
    CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling);
    // 结果为:Car{brand='wuling', price=6666.66, onMarket=true}
    System.out.println("结果为:" + wulingDTO);
}

可以看到,mapstruct很好地完成了我们的目标,那么它是如何做到的呢?我们查看CarMapper.INSTANCE.carToCarDTO(wuling)的实现类,可以看到在编译过程中自动生成了如下内容的接口实现类:

public class CarMapperImpl implements CarMapper {
    @Override
    public CarDTO carToCarDTO(Car car) {
        if ( car == null ) {
            return null;
        }
        CarDTO carDTO = new CarDTO();
        carDTO.setBrand( car.getBrand() );
        carDTO.setPrice( car.getPrice() );
        carDTO.setOnMarket( car.getOnMarket() );
        return carDTO;
    }
}

所以,mapstruct并没有使用反射的机制,而是使用了普通的set和get方法来进行属性拷贝的,因此要求我们的对象也一定要有set和get方法。


2.2.2 不同属性名映射


在如上示例中,我们源对象和目标对象的属性名称全都一致,但是在很多的场景下,源对象和目标对象的同一个字段很可能名称是不同的,这种情况下,只需要在映射接口类中指定即可:


public class Car {
    ...
    private Boolean onMarket;
    ...
    // setters + getters + toString
}
public class Car {
    ...
    private Boolean onMarket;
    ...
    // setters + getters + toString
}
@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    @Mapping(source = "car.onMarket", target = "onSale")
    CarDTO carToCarDTO(Car car);
}

如此,生成的接口实现类如下:

@Override
public CarDTO carToCarDTO(Car car) {
    if ( car == null ) {
        return null;
    }
    CarDTO carDTO = new CarDTO();
    carDTO.setOnSale( car.getOnMarket() );
    carDTO.setBrand( car.getBrand() );
    carDTO.setPrice( car.getPrice() );
    return carDTO;
}


2.2.3 不同个数属性映射


我们假设Car和CarDTO各有一个对方没有的属性,那么在进行对象拷贝时会发生什么?


public class Car {
    ...
    private Date birthdate;
    // setters + getters + toString
}
public class CarDTO {
    ...
    private String owner;
    // setters + getters + toString
}

p

@Test
public void test1(){
    Car wuling = new Car();
    wuling.setBrand("wuling");
    wuling.setPrice(6666.66);
    wuling.setOnMarket(true);
    wuling.setBirthdate(new Date());
    CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling);
    System.out.println("结果为:" + wulingDTO);
}

然后我们执行如上转换的案例,发现并没有报错,从Car拷贝属性到CarDTO时,CarDTO由于没有birthdate属性,则不会赋值;同时,CarDTO的owner因为Car中没有,因此也不会被赋值,生成的接口实现类如下:

@Override
public CarDTO carToCarDTO(Car car) {
    if ( car == null ) {
        return null;
    }
    CarDTO carDTO = new CarDTO();
    carDTO.setOnSale( car.getOnMarket() );
    carDTO.setBrand( car.getBrand() );
    carDTO.setPrice( car.getPrice() );
    return carDTO;
}

因此,mapstruct只会对共有的交集属性进行拷贝操作。


2.2.4 多个源合并映射


我们新增一个Person类,其中的name属性对应CarDTO中的owner属性。

public class Person {
    private String name;
    private String age;
    // setters + getters + toString
}
@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    @Mapping(source = "car.onMarket", target = "onSale")
    @Mapping(source = "person.name", target = "owner")
    CarDTO carToCarDTO(Car car, Person person);
}

自动生成的接口实现类如下:

@Override
public CarDTO carToCarDTO(Car car, Person person) {
    if ( car == null && person == null ) {
        return null;
    }
    CarDTO carDTO = new CarDTO();
    if ( car != null ) {
        carDTO.setOnSale( car.getOnMarket() );
        carDTO.setBrand( car.getBrand() );
        carDTO.setPrice( car.getPrice() );
    }
    if ( person != null ) {
        carDTO.setOwner( person.getName() );
    }
    return carDTO;
}


2.2.5 子对象映射


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

public class Person {
    private String name;
    private String age;
    // setters + getters + toString
}
public class PersonDTO {
    private String name;
    private String age;
    // setters + getters + toString
}
public class Car {
    private String brand;
    private Double price;
    private Boolean onMarket;
    private Person owner;
    // setters + getters + toString
}
public class CarDTO {
    private String brand;
    private Double price;
    private Boolean onMarket;
    private PersonDTO owner;
    // setters + getters + toString
}
@Mapper
public interface PersonMapper {
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
    PersonDTO personToPersonDTO(Person person);
}
@Mapper(uses = {PersonMapper.class})
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    @Mapping(source = "onMarket", target = "onSale")
    CarDTO carToCarDTO(Car car);
}
@Test
public void test1(){
    Person jack = new Person();
    jack.setName("jack");
    jack.setAge("22");
    Car wuling = new Car();
    wuling.setBrand("wuling");
    wuling.setPrice(6666.66);
    wuling.setOnMarket(true);
    wuling.setOwner(jack);
    CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling);
    // 结果为:CarDTO{brand='wuling', price=6666.66, onSale=true, owner=Person{name='jack', age='22'}}
    System.out.println("结果为:" + wulingDTO);
}

这里最重要的是:

  • 需要增加PersonMapper接口,让mapstruct能够对Person和PersonDTO进行转换;
  • CarMapper中需要引入PersonMapper,如果存在多个对象属性,此处就要引入多个对象属性的Mapper接口;
相关文章
|
2月前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
72 11
|
2月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
74 7
|
10天前
|
Java
Java快速入门之类、对象、方法
本文简要介绍了Java快速入门中的类、对象和方法。首先,解释了类和对象的概念,类是对象的抽象,对象是类的具体实例。接着,阐述了类的定义和组成,包括属性和行为,并展示了如何创建和使用对象。然后,讨论了成员变量与局部变量的区别,强调了封装的重要性,通过`private`关键字隐藏数据并提供`get/set`方法访问。最后,介绍了构造方法的定义和重载,以及标准类的制作规范,帮助初学者理解如何构建完整的Java类。
|
9天前
|
安全 Java
Object取值转java对象
通过本文的介绍,我们了解了几种将 `Object`类型转换为Java对象的方法,包括强制类型转换、使用 `instanceof`检查类型和泛型方法等。此外,还探讨了在集合、反射和序列化等常见场景中的应用。掌握这些方法和技巧,有助于编写更健壮和类型安全的Java代码。
30 17
|
24天前
|
Java
java代码优化:判断内聚到实体对象中和构造上下文对象传递参数
通过两个常见的java后端实例场景探讨代码优化,代码不是优化出来的,而是设计出来的,我们永远不可能有专门的时间去做代码优化,优化和设计在平时
30 15
|
2月前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
24天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
2月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
3月前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。
|
27天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
85 17