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

 更新时间:2022年09月02日 08:34:00   作者:指北君  
在我们日常开发的程序中,为了各层之间解耦,一般会定义不同的对象用来在不同层之间传递数据。当在不同层之间传输数据时,不可避免地经常需要将这些对象进行相互转换。今天给大家介绍一个对象转换工具MapStruct,代码简洁安全、性能高,强烈推荐

前言

在我们日常开发的程序中,为了各层之间解耦,一般会定义不同的对象用来在不同层之间传递数据,比如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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入浅出Java中的字节流和字符流详解

    深入浅出Java中的字节流和字符流详解

    Java 中的输入输出(I/O)流主要分为字节流和字符流,这两类流为开发者提供了高效的文件读写方式,也解决了不同编码格式下的字符处理问题,本文将带你深入了解字节流和字符流的区别、应用场景以及如何使用它们处理文件操作
    2024-12-12
  • SpringMVC中解决@ResponseBody注解返回中文乱码问题

    SpringMVC中解决@ResponseBody注解返回中文乱码问题

    这篇文章主要介绍了SpringMVC中解决@ResponseBody注解返回中文乱码问题, 小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • Java实现的zip压缩及解压缩工具类示例

    Java实现的zip压缩及解压缩工具类示例

    这篇文章主要介绍了Java实现的zip压缩及解压缩工具类,结合实例形式分析了java对文件的进行zip压缩及解压缩的具体操作技巧,需要的朋友可以参考下
    2018-01-01
  • 浅谈JVM垃圾回收有哪些常用算法

    浅谈JVM垃圾回收有哪些常用算法

    今天给大家带来的是关于Java虚拟机的相关知识,文章围绕着JVM垃圾回收有哪些常用算法展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • Java常用排序算法及性能测试集合

    Java常用排序算法及性能测试集合

    周末天气不好,在家无事,把常用排序算法理了一遍,收获不小,特写文章纪念。这些算法在学校的时候学过一遍,很多原理都忘记了
    2013-06-06
  • SpringAOP 构造注入的实现步骤

    SpringAOP 构造注入的实现步骤

    这篇文章主要介绍了SpringAOP_构造注入的实现步骤,帮助大家更好的理解和学习使用spring框架,感兴趣的朋友可以了解下
    2021-05-05
  • Java 动态编译在项目中的实践分享

    Java 动态编译在项目中的实践分享

    在 Java 中,动态编译是指在运行时动态地编译 Java 源代码,生成字节码,并加载到 JVM 中执行,动态编译可以用于实现动态代码生成、动态加载、插件化等功能,本文将给大家分享一下Java 动态编译在项目中的实践,感兴趣的同学跟着小编一起来看看吧
    2023-07-07
  • Java深入讲解static操作符

    Java深入讲解static操作符

    static关键字基本概念我们可以一句话来概括:方便在没有创建对象的情况下来进行调用。也就是说:被static关键字修饰的不需要创建对象去调用,直接根据类名就可以去访问,让我们来了解一下你可能还不知道情况
    2022-07-07
  • Java Scanner类及其方法使用图解

    Java Scanner类及其方法使用图解

    这篇文章主要介绍了Java Scanner类及其方法使用图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • 简单记事本java源码实例

    简单记事本java源码实例

    这篇文章主要介绍了简单记事本java源码,以一个完整的实例形式分析了记事本的Java实现方法,对于Java应用程序的开发有一定的参考借鉴价值,需要的朋友可以参考下
    2014-11-11

最新评论