前言
也不知道说啥,那就直入主题吧~
什么是MapStruct? 介绍 相信大家,已经在各种地方都听过MapStruct
这个框架了,知道它是一个类似于BeanUtils
的拷贝框架,那么下面让我们来看下官方的介绍
MapStruct is a Java annotation processor for the generation of type-safe and performant mappers for Java bean classes. It saves you from writing mapping code by hand, which is a tedious and error-prone task. The generator comes with sensible defaults and many built-in type conversions, but it steps out of your way when it comes to configuring or implementing special behavior.
翻译一下就是:MapStruct是一个基于Java编译期注解处理器 来实现,用来高性能、类型安全的生成Java bean的映射器。可以避免我们手动写Getter/Getter等映射代码。
在上面的介绍中可以发现一个词Java annotation processor
,翻译成中文就是Java编译期注解处理器
,那么什么是Java编译期注解处理器呢?
Java编译处理器(APT) Java编译时期处理器可以简称为APT
,它是Javac
的工具,最早出现在JDK5
的版本中,随后在之后的版本不断新增相关API,开始流行起来。
通过APT
可以拿到注解和被注解对象的相关信息,随后通过自身需求来自动生成一些代码,避免手动编写。例如我们常用的Lombok
就有采用APT
来生成相关代码。
同时,获取注解和自定义生成代码等操作都是在编译期完成的,不会影响运行时期的程序性能。不过有一点需要注意,APT
只能用于生成新文件,不能更改现有的文件。
基本原理 通过官方介绍已经知道MapStruct
是通过Java编译期注解处理器也就是APT
来实现的。那么具体是怎么样实现的呢?
实现一个APT首先需要继承AbstractProcessor
或者实现Processor
接口,基本都是继承抽象类来实现的。
随后在创建META-INF/services/javax.annotation.processing.Processor
文件,并在里面注册自定义的Annotation Processor
。
通过查看[MapStruct](mapstruct/mapstruct: An annotation processor for generating type-safe bean mappers (github.com) )的源码,就可以发现它定义了一个MappingProcessor
的类同时继承了AbstractProcessor
来实现相应功能。并在javax.annotation.processing.Processor
文件中注册了MappingProcessor
类。
MapStruct
的具体怎么生成Mapper实现类
先不鸟他,下面就来看看怎么使用吧~
优缺点 在使用前,先来看一波MapStruct
的优缺点,在实践中就能切身体会到优缺点拉。
先吹优点:
高性能映射对象 ,相关于反射来实现的性能快将近20倍,差不多很接近原生的Getter/Setter方法。如果想要看具体性能比较可以参考这篇文章Performance of Java Mapping Frameworks | Baeldung 。
编译时期安全,不会出现错误对象的映射,例如不会出现将商品
映射成用户DTO。
仅在编译时期工作,不会有运行期依赖
那么代价是啥捏?就我个人而言认为缺点只有一个:繁琐
,相关于常用Spring中的BeanUtils
编写较为繁琐,会增加工作量,不能够准时下班拉。
那么下面就来通过实例来了解MapStruct吧
如何简单使用 环境配置
Jdk11
MapStruct 1.5.0.RC1
需要注意一点,MapStruct
只支持1.8或者更高的版本。
Maven配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ... <properties > <org.mapstruct.version > 1.5.0.RC1</org.mapstruct.version > </properties > ... <dependencies > <dependency > <groupId > org.mapstruct</groupId > <artifactId > mapstruct</artifactId > <version > ${org.mapstruct.version}</version > </dependency > </dependencies > ... <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.8.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 > ...
入门例子 先定两个平平无奇的模型类Goods
和GoodsDto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Goods { private Integer id; private String name; private Long price; } public class GoodsDto { private Integer id; private String goodsName; private Long price; private LocalDate createTime; }
随后定义Mapper接口
1 2 3 4 5 6 7 8 @Mapper public interface GoodsMapper { GoodsMapper INSTANCE = Mappers.getMapper(GoodsMapper.class ) ; @Mapping (source = "name" ,target = "goodsName" ) GoodsDto goodsToGoodsDto (Goods goods) ; }
然后编译代码,跑一下测试方法,输出GoodsDto
属性结果,然后你就可以说掌握MapStruct
(^_^ )拉。
1 2 3 4 5 6 7 8 @Test void shouldMapGoodsToDto () { Goods goods = new Goods(1 ,"3060TI显卡" ,299900L ); GoodsDto goodsDto = GoodsMapper.INSTANCE.goodsToGoodsDto(goods); log.info("goodsDto:{}" ,goodsDto); }
分析一下 通过上面的例子,可以知道对于类型和名字一致的属性对自动映射,而对于属性名不一致的时候就需要手动声明一下。同时对于未匹配的属性字段在编译时期会有一个警告⚠信息,例如下面的编译告警信息。
1 2 D:\java -workspace\mapstruct -example\src \test \java \pers \czj \mapstruct \example 01\GoodsMapper .java:13:14 java: Unmapped target property: "createTime".
我们可以在target的目录下看到MapStruct
生成的Mapper实现类
,下面的代码就是GoodsMapper
接口的生成。可以看到当我们使用goodsToGoodsDto
方法时本质上就是调用对象的Getter/Setter方法,这就是为什么MapStruct
是高性能的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package pers.czj.mapstruct.example01;import javax.annotation.processing.Generated;@Generated ( value = "org.mapstruct.ap.MappingProcessor" , date = "2022-06-02T15:36:45+0800" , comments = "version: 1.5.0.RC1, compiler: javac, environment: Java 11.0.13 (Oracle Corporation)" ) public class GoodsMapperImpl implements GoodsMapper { @Override public GoodsDto goodsToGoodsDto (Goods goods) { if ( goods == null ) { return null ; } GoodsDto goodsDto = new GoodsDto(); goodsDto.setGoodsName( goods.getName() ); goodsDto.setId( goods.getId() ); goodsDto.setPrice( goods.getPrice() ); return goodsDto; } }
对象映射 不同名字段映射 使用上面例子的@Mapping(source = "name",target = "goodsName")
,如果有多个映射属性名不一致就定义多个@Mapping注解。
自动映射类型
String类和包装类型的互相自动转换,例如String
转Integer
等。
基本类型和其包装类型的互相转换
String和枚举类型之间的互相转换
结合lombok 在同时使用MapStruct
和Lombok
的时候,需要注意编译顺序问题,当Lombok
的Source Code
未生成时,生成的Mapper
实现,将会缺少很多属性字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.8.1</version > <configuration > <source > 11</source > <target > 11</target > <annotationProcessorPaths > <path > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > </path > <path > <groupId > org.mapstruct</groupId > <artifactId > mapstruct-processor</artifactId > <version > ${org.mapstruct.version}</version > </path > </annotationProcessorPaths > </configuration > </plugin >
多个数据源同时映射 MapStruct
也支持多个数据源同时映射一个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @Data @AllArgsConstructor @NoArgsConstructor public class Goods { private Integer id; private String name; private Long price; } @Data @AllArgsConstructor @NoArgsConstructor public class GoodsInfo { private Integer id; private String sourceAddress; private LocalDate createTime; private String description; } @Data public class GoodsDto { private Integer id; private Long price; private String goodsName; private String sourceAddress; private String description; private LocalDate createTime; }
1 2 3 4 5 6 7 8 9 10 11 @Mapper public interface GoodsMapper { GoodsMapper INSTANCE = Mappers.getMapper(GoodsMapper.class ) ; @Mapping (source = "goods.name" ,target = "goodsName" ) @Mapping (source = "goods.id" ,target = "id" ) @Mapping (source = "info" ,target = "." ) GoodsDto goodsAndInfoToGoodsDto (Goods goods,GoodsInfo info) ; }
这里使用了.
将告诉MapStruct
将源Bean的每个属性都映射到目标对象中。
更新已有实例 1 2 3 4 @Mapping (source = "goods.name" ,target = "goodsName" )@Mapping (source = "goods.id" ,target = "id" )@Mapping (source = "info" ,target = "." )void updateGoodsDto (Goods goods, GoodsInfo info, @MappingTarget GoodsDto goodsDto) ;
使用@MappingTarget
注解标明要更新的实例
更多操作 MapStruct
还支持很多的操作,其他操作下篇在介绍拉~
观点 通过本文的一些例子,相信大家对于MapStruct
有一丢丢的了解,它的优势就是高性能,通过APT来自动生成映射对象之间的Getter/Setter方法。当然他的劣势也比较明显,即繁琐。对于每个对象之间的映射都需要定义对应的Mapper
接口,还需要处理一些类型转换,字段名不匹配等问题,会添加许多工作量。不过这并不妨碍MapStruct
是一个优秀的对象映射框架。
如果你的项目追求高性能,那么就可以使用MapStruct
。如果你的项目是普通的CRUD,并且产品还天天催那就还是选择BeanUtils
一把梭,毕竟早点下班才是真谛~。