详解Java对象转换神器MapStruct库的使用

目录
  • 前言
  • MapStruct简介
  • MapStruct入门
    • 1. 引入依赖
    • 2. 需要转换的对象
    • 3. 创建转换器
    • 4. 验证
    • 5. 自动生成的实现类
  • MapStruct进阶
    • 场景1:属性名称不同、(基本)类型不同
    • 场景2:统一映射不同类型
    • 场景3:固定值、忽略某个属性、时间转字符串格式
    • 场景4:为某个属性指定转换方法
    • 场景5:多个参数合并为一个对象
    • 场景6:已有目标对象,将源对象属性覆盖到目标对象
    • 场景7:源对象两个属性合并为一个属性
  • 小结

前言

在我们日常开发的程序中,为了各层之间解耦,一般会定义不同的对象用来在不同层之间传递数据,比如xxxDTO、xxxVO、xxxQO,当在不同层之间传输数据时,不可避免地经常需要将这些对象进行相互转换。

今天给大家介绍一个对象转换工具MapStruct,代码简洁安全、性能高,强烈推荐。

MapStruct简介

MapStruct是一个代码生成器,它基于约定优于配置,极大地简化了Java Bean类型之间映射的实现。特点如下:

  • 基于注解
  • 在编译期自动生成映射转换代码
  • 类型安全、高性能、无依赖性、易于理解阅读

MapStruct入门

1. 引入依赖

这里使用Gradle构建

dependencies {
    implementation 'org.mapstruct:mapstruct:1.4.2.Final'
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}

2. 需要转换的对象

创建两个示例对象(e.g. 将Demo对象转换为DemoDto对象)

/**
 * 源对象
 */
@Data
public class Demo {
    private Integer id;
    private String name;
}

/**
 * 目标对象
 */
@Data
public class DemoDto {
    private Integer id;
    private String name;
}

3. 创建转换器

只需要创建一个转换器接口类,并在类上添加 @Mapper 注解即可(官方示例推荐以 xxxMapper 格式命名转换器名称)

@Mapper
public interface DemoMapper {
    //使用Mappers工厂获取DemoMapper实现类
    DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);
    //定义接口方法,参数为来源对象,返回值为目标对象
    DemoDto toDemoDto(Demo demo);
}

4. 验证

public static void main(String[] args) {
    Demo demo = new Demo();
    demo.setId(111);
    demo.setName("hello");

    DemoDto demoDto = DemoMapper.INSTANCE.toDemoDto(demo);

    System.out.println("目标对象demoDto为:" + demoDto);
    //输出结果:目标对象demoDto为:DemoDto(id=111, name=hello)
}

测试结果如下:

目标对象demoDto为:DemoDto(id=111, name=hello)

达到了我们的预期结果。

5. 自动生成的实现类

为什么声明一个接口就可以转换对象呢?我们看一下MapStruct在编译期间自动生成的实现类:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2022-09-01T17:54:38+0800",
    comments = "version: 1.4.2.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-7.3.jar, environment: Java 1.8.0_231 (Oracle Corporation)"
)
public class DemoMapperImpl implements DemoMapper {

    @Override
    public DemoDto toDemoDto(Demo demo) {
        if ( demo == null ) {
            return null;
        }

        DemoDto demoDto = new DemoDto();

        demoDto.setId( demo.getId() );
        demoDto.setName( demo.getName() );

        return demoDto;
    }
}

可以看到,MapStruct帮我们将繁杂的代码自动生成了,而且实现类中用的都是最基本的get、set方法,易于阅读理解,转换速度非常快。

MapStruct进阶

上面的例子只是小试牛刀,下面开始展示MapStruct的强大之处。

(限于篇幅,这里不展示自动生成的实现类和验证结果,大家可自行测试)

场景1:属性名称不同、(基本)类型不同

  • 属性名称不同: 在方法上加上 @Mapping 注解,用来映射属性
  • 属性基本类型不同: 基本类型和String等类型会自动转换

关键字:@Mapping注解

/**
 * 来源对象
 */
@Data
public class Demo {
    private Integer id;
    private String name;
}

/**
 * 目标对象
 */
@Data
public class DemoDto {
    private String id;
    private String fullname;
}

/**
 * 转换器
 */
@Mapper
public interface DemoMapper {
    DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);

    @Mapping(target = "fullname", source = "name")
    DemoDto toDemoDto(Demo demo);
}

场景2:统一映射不同类型

下面例子中,time1、time2、time3都会被转换,具体说明看下面的注释:

/**
 * 来源对象
 */
@Data
public class Demo {
    private Integer id;
    private String name;
    /**
     * time1、time2名称相同,time3转为time33
     * 这里的time1、time2、time33都是Date类型
     */
    private Date time1;
    private Date time2;
    private Date time3;
}

/**
 * 目标对象
 */
@Data
public class DemoDto {
    private String id;
    private String name;
    /**
     * 这里的time1、time2、time33都是String类型
     */
    private String time1;
    private String time2;
    private String time33;
}

/**
 * 转换器
 */
@Mapper
public interface DemoMapper {
    DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);

    @Mapping(target = "time33", source = "time3")
    DemoDto toDemoDto(Demo demo);
    
    //MapStruct会将所有匹配到的:
    //源类型为Date、目标类型为String的属性,
    //按以下方法进行转换
    static String date2String(Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strDate = simpleDateFormat.format(date);
        return strDate;
    }
}

场景3:固定值、忽略某个属性、时间转字符串格式

一个例子演示三种用法,具体说明看注释,很容易理解:

关键字:ignore、constant、dateFormat

/**
 * 来源对象
 */
@Data
public class Demo {
    private Integer id;
    private String name;
    private Date time;
}

/**
 * 目标对象
 */
@Data
public class DemoDto {
    private String id;
    private String name;
    private String time;
}

/**
 * 转换器
 */
@Mapper
public interface DemoMapper {
    DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);

    //id属性不赋值
    @Mapping(target = "id", ignore = true)
    //name属性固定赋值为“hello”
    @Mapping(target = "name", constant = "hello")
    //time属性转为yyyy-MM-dd HH:mm:ss格式的字符串
    @Mapping(target = "time", dateFormat = "yyyy-MM-dd HH:mm:ss")
    DemoDto toDemoDto(Demo demo);
}

场景4:为某个属性指定转换方法

场景2中,我们是按照某个转换方法,统一将一种类型转换为另外一种类型;而下面这个例子,是为某个属性指定方法:

关键字:@Named注解、qualifiedByName

/**
 * 来源对象
 */
@Data
public class Demo {
    private Integer id;
    private String name;
}

/**
 * 目标对象
 */
@Data
public class DemoDto {
    private String id;
    private String name;
}

/**
 * 转换器
 */
@Mapper
public interface DemoMapper {
    DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);

    //为name属性指定@Named为convertName的方法进行转换
    @Mapping(target = "name", qualifiedByName = "convertName")
    DemoDto toDemoDto(Demo demo);

    @Named("convertName")
    static String aaa(String name) {
        return "姓名为:" + name;
    }
}

场景5:多个参数合并为一个对象

如果参数为多个的话,@Mapping注解中的source就要指定是哪个参数了,用点分隔:

关键字:点(.)

/**
 * 来源对象
 */
@Data
public class Demo {
    private Integer id;
    private String name;
}

/**
 * 目标对象
 */
@Data
public class DemoDto {
    private String fullname;
    private String timestamp;
}

/**
 * 转换器
 */
@Mapper
public interface DemoMapper {
    DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);

    //fullname属性赋值demo对象的name属性(注意这里.的用法)
    //timestamp属性赋值为传入的time参数
    @Mapping(target = "fullname", source = "demo.name")
    @Mapping(target = "timestamp", source = "time")
    DemoDto toDemoDto(Demo demo, String time);
}

场景6:已有目标对象,将源对象属性覆盖到目标对象

覆盖目标对象属性时,一般null值不覆盖,所以需要在类上的@Mapper注解中添加属性:nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE 代表null值不进行赋值。

关键字:@MappingTarget注解、nullValuePropertyMappingStrategy

/**
 * 来源对象
 */
@Data
public class Demo {
    private Integer id;
    private String name;
}

/**
 * 目标对象
 */
@Data
public class DemoDto {
    private String id;
    private String name;
}

/**
 * 转换器
 */
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE,
        nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface DemoMapper {
    DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);

    //将已有的目标对象当作一个参数传进来
    DemoDto toDemoDto(Demo demo, @MappingTarget DemoDto dto);
}

场景7:源对象两个属性合并为一个属性

这种情况可以使用@AfterMapping注解。

关键字:@AfterMapping注解、@MappingTarget注解

/**
 * 来源对象
 */
@Data
public class Demo {
    private Integer id;
    private String firstName;
    private String lastName;
}

/**
 * 目标对象
 */
@Data
public class DemoDto {
    private String id;
    private String name;
}

/**
 * 转换器
 */
@Mapper
public interface DemoMapper {
    DemoMapper INSTANCE = Mappers.getMapper(DemoMapper.class);

    DemoDto toDemoDto(Demo demo);

    //在转换完成后执行的方法,一般用到源对象两个属性合并为一个属性的场景
    //需要将源对象、目标对象(@MappingTarget)都作为参数传进来,
    @AfterMapping
    static void afterToDemoDto(Demo demo, @MappingTarget DemoDto demoDto) {
        String name = demo.getFirstName() + demo.getLastName();
        demoDto.setName(name);
    }
}

小结

本文介绍了对象转换工具 MapStruct 库,以安全、简洁、优雅的方式来优化我们的转换代码。

从文中的示例场景中可以看出,MapStruct 提供了大量的功能和配置,使我们可以快捷的创建出各种或简单或复杂的映射器。而这些,也只是 MapStruct 库的冰山一角,还有很多强大的功能文中没有提到,感兴趣的朋友可以自行查看官方文档。

到此这篇关于详解Java对象转换神器MapStruct库的使用的文章就介绍到这了,更多相关Java MapStruct内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java编码辅助工具Mapstruct用法详解

    前言 项目开发中,业务分层会涉及不同类型的Bean之间需要相互转换,如PO与DTO之间,PO与VO之间等.手动编码setter/getter各个对应属性,会显得臃肿繁琐.通过Mapstruct框架可简单方便地完成这一工作. 如何引入: IntelliJ IDEA中安装MapStruct Support插件:File -> Settings -> Plugins 搜索 MapStruct support 安装,同时File -> Settings -> Compiler ->

  • Java MapStruct解了对象映射的毒

    前言 MVC模式是目前主流项目的标准开发模式,这种模式下框架的分层结构清晰,主要分为Controller,Service,Dao.分层的结构下,各层之间的数据传输要求就会存在差异,我们不能用一个对象来贯穿3层,这样不符合开发规范且不够灵活. 我们常常会遇到层级之间字段格式需求不一致的情况,例如数据库中某个字段是datetime日期格式,这个时间戳在数据库中的存储值为2020-11-06 23:59:59.999999,但是传递给前端的时候要求接口返回yyyy-MM-dd的格式,或者有些数据在数据

  • Java中的MapStruct用法详解

    目录 1 MapStruct配置 2 原理&性能 2.1 实现原理 3 使用方法 3.1 转换器的检索 3.1.1 使用Mappers工厂获取 3.1.2 通过依赖注入的方式获取 3.2 简单映射 3.2.1 基本映射 3.2.2 多源参数映射 3.2.3 更新对象 3.3 数据类型转换 3.3.1 对于基础数据类型会进行自动隐式的转换 3.3.2 指定转换格式 3.3.3 属性为复杂对象的映射 3.3.4 自定义转换器 3.3.5 使用限定符限定使用转换方法 3.4 Map的映射 3.5 枚举

  • 详解Java中的mapstruct插件使用

    实体类的属性映射怎么可以少了它? 我们都知道,随着一个工程的越来越成熟,模块划分会越来越细,其中实体类一般存于 domain 之中,但 domain 工程最好不要被其他工程依赖,所以其他工程想获取实体类数据时就需要在各自工程写 model,自定义 model 可以根据自身业务需要映射相应的实体属性.这样一来,这个映射工程貌似并不简单了.阿森差点就犯难了…… 序 所以阿淼今天就要给大家安利一款叫 mapstruct 的插件,它就是专门用来处理 domin 实体类与 model 类的属性映射的,我们

  • Java实体映射工具MapStruct使用方法详解

    目录 1.序 2.简单用例 3.使用详解 1)关于接口注解@Mapper几种属性用法详解 2) 其他方法级别注解 总结 1.序 通常在后端开发中经常不直接返回实体Entity类,经过处理转换返回前端,前端提交过来的对象也需要经过转换Entity实体才做存储:通常使用的BeanUtils.copyProperties方法也比较粗暴,不仅效率低下(使用反射)而且仅映射相同名的属性,多数情况下还需要手动编写对应的转换方法实现. 插件MapStruct以接口方法结合注解优雅实现对象转换,MapStruc

  • 详解Java对象转换神器MapStruct库的使用

    目录 前言 MapStruct简介 MapStruct入门 1. 引入依赖 2. 需要转换的对象 3. 创建转换器 4. 验证 5. 自动生成的实现类 MapStruct进阶 场景1:属性名称不同.(基本)类型不同 场景2:统一映射不同类型 场景3:固定值.忽略某个属性.时间转字符串格式 场景4:为某个属性指定转换方法 场景5:多个参数合并为一个对象 场景6:已有目标对象,将源对象属性覆盖到目标对象 场景7:源对象两个属性合并为一个属性 小结 前言 在我们日常开发的程序中,为了各层之间解耦,一般

  • 详解Java对象的强、软、弱和虚引用+ReferenceQueue

    详解Java对象的强.软.弱和虚引用+ReferenceQueue 一.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题. 二.软引用(SoftReference) 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它:如果内存空间不足了,就会回收这些对象的内存.只要垃圾回

  • 详解java 对象锁与类锁

    一.什么是对象锁 对象锁也叫方法锁,是针对一个对象实例的,它只在该对象的某个内存位置声明一个标识该对象是否拥有锁,所有它只会锁住当前的对象,而并不会对其他对象实例的锁产生任何影响,不同对象访问同一个被synchronized修饰的方法的时候不会阻塞, 例如: public class MyObject { private synchronized void method1(){ try { System.out.println(Thread.currentThread().getName());

  • 详解Java对象序列化为什么要使用SerialversionUID

    1.首先谈谈为什么要序列化对象 - 把对象转换为字节序列的过程称为对象的序列化. - 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中: 2) 在网络上传送对象的字节序列. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器

  • 详解Java对象的内存布局

    前言 今天来讲些抽象的东西 -- 对象头,因为我在学习的过程中发现很多地方都关联到了对象头的知识点,例如JDK中的 synchronized锁优化 和 JVM 中对象年龄升级等等.要深入理解这些知识的原理,了解对象头的概念很有必要,而且可以为后面分享 synchronized 原理和 JVM 知识的时候做准备. 对象内存构成 Java 中通过 new 关键字创建一个类的实例对象,对象存于内存的堆中并给其分配一个内存地址,那么是否想过如下这些问题: 这个实例对象是以怎样的形态存在内存中的? 一个O

  • 详解Java对象创建的过程及内存布局

    一.对象的内存布局 对象头 对象头主要保存对象自身的运行时数据和用于指定该对象属于哪个类的类型指针. 实例数据 保存对象的有效数据,例如对象的字段信息,其中包括从父类继承下来的. 对齐填充 对齐填充不是必须存在的,没有特别的含义,只起到一个占位符的作用. 二.对象的创建过程 实例化一个类的对象的过程是一个典型的递归过程. 在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类. 此时,首先实例化Object类

  • 详解Java 对象序列化和反序列化

    之前的文章中我们介绍过有关字节流字符流的使用,当时我们对于将一个对象输出到流中的操作,使用DataOutputStream流将该对象中的每个属性值逐个输出到流中,读出时相反.在我们看来这种行为实在是繁琐,尤其是在这个对象中属性值很多的时候.基于此,Java中对象的序列化机制就可以很好的解决这种操作.本篇就简单的介绍Java对象序列化,主要内容如下: 简洁的代码实现 序列化实现的基本算法 两种特殊的情况 自定义序列化机制 序列化的版本控制 一.简洁的代码实现 在介绍对象序列化的使用方法之前,先看看

  • 详解Java对象结构与对象锁的升级

    目录 1.Java对象结构 2.MarkWord的结构信息 3.无锁.偏向锁.轻量级锁和重量级锁 总结 1. Java对象结构 Java对象结构包括三部分:对象头.对象体和填充字节,如图所示: 对象头又包括三个字段: 第一个字段叫作Mark Word(标记字),用于存储自身运行时的数据,例如GC标志位.哈希码.锁状态等信息. 第二个字段叫作Class Pointer(类对象指针),用于存放方法区Class对象的地址,虚拟机通过这个指针来确定这个对象是哪个类的实例. 第三个字段叫作Array Le

  • 详解 Java中日期数据类型的处理之格式转换的实例

    详解 Java中日期数据类型的处理之格式转换的实例 概要: 日期以及时间格式处理,在Java中时间格式一般会涉及到的数据类型包括Calendar类和Date类. Date类: 1.Date类型转String类型(以时间格式1970-01-01 01:01:01为例) //yyyy-MM-dd HH:mm:ss表示24时间进制 SimpleDateFormat sDateFormat=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String

随机推荐