MapStruct从入门到出门(一)

前言

也不知道说啥,那就直入主题吧~

什么是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只能用于生成新文件,不能更改现有的文件。

img

基本原理

通过官方介绍已经知道MapStruct是通过Java编译期注解处理器也就是APT来实现的。那么具体是怎么样实现的呢?

  1. 实现一个APT首先需要继承AbstractProcessor或者实现Processor接口,基本都是继承抽象类来实现的。
  2. 随后在创建META-INF/services/javax.annotation.processing.Processor文件,并在里面注册自定义的Annotation Processor

通过查看MapStruct的源码,就可以发现它定义了一个MappingProcessor的类同时继承了AbstractProcessor来实现相应功能。并在javax.annotation.processing.Processor文件中注册了MappingProcessor类。

扫描二维码关注公众号,回复: 14225441 查看本文章

image.png

image.png

MapStruct的具体怎么生成Mapper实现类先不鸟他,下面就来看看怎么使用吧~

优缺点

在使用前,先来看一波MapStruct的优缺点,在实践中就能切身体会到优缺点拉。

先吹优点:

  1. 高性能映射对象,相关于反射来实现的性能快将近20倍,差不多很接近原生的Getter/Setter方法。如果想要看具体性能比较可以参考这篇文章Performance of Java Mapping Frameworks | Baeldung
  2. 编译时期安全,不会出现错误对象的映射,例如不会出现将商品映射成用户DTO。
  3. 仅在编译时期工作,不会有运行期依赖

那么代价是啥捏?就我个人而言认为缺点只有一个:繁琐,相关于常用Spring中的BeanUtils编写较为繁琐,会增加工作量,不能够准时下班了。

那么下面就来通过实例来了解MapStruct吧

如何简单使用

环境配置

  • Jdk11
  • MapStruct 1.5.0.RC1

需要注意一点,MapStruct只支持1.8或者更高的版本。

Maven配置:

...
<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>
...
复制代码

入门例子

先定两个平平无奇的模型类GoodsGoodsDto

//Goods.java
public class Goods {
​
    private Integer id;
​
    private String name;
​
    private Long price;
   
    //...省略构造方法和Getter/Setter方法
}
​
//GoodsDto.java
public class GoodsDto {
​
    private Integer id;
​
    private String goodsName;
​
    private Long price;
​
    private LocalDate createTime;
​
    //...省略构造方法和Getter/Setter方法
}
复制代码

随后定义Mapper接口

@Mapper
public interface GoodsMapper {
​
    GoodsMapper INSTANCE = Mappers.getMapper(GoodsMapper.class);
​
    @Mapping(source = "name",target = "goodsName")
    GoodsDto goodsToGoodsDto(Goods goods);
}
​
复制代码

然后编译代码,跑一下测试方法,输出GoodsDto属性结果,然后你就可以说掌握MapStruct( ^_^ )拉。

    @Test
    void shouldMapGoodsToDto(){
        Goods goods = new Goods(1,"3060TI显卡",299900L);
        GoodsDto goodsDto = GoodsMapper.INSTANCE.goodsToGoodsDto(goods);
        log.info("goodsDto:{}",goodsDto);
    }
​
// goodsDto:GoodsDto{id=1, goodsName='3060TI显卡', price=299900, createTime=null}
复制代码
分析一下

通过上面的例子,可以知道对于类型和名字一致的属性对自动映射,而对于属性名不一致的时候就需要手动声明一下。同时对于未匹配的属性字段在编译时期会有一个警告⚠信息,例如下面的编译告警信息。

D:\java-workspace\mapstruct-example\src\test\java\pers\czj\mapstruct\example01\GoodsMapper.java:13:14
java: Unmapped target property: "createTime".
复制代码

我们可以在target的目录下看到MapStruct生成的Mapper实现类,下面的代码就是GoodsMapper接口的生成。可以看到当我们使用goodsToGoodsDto方法时本质上就是调用对象的Getter/Setter方法,这就是为什么MapStruct是高性能的原因。

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类和包装类型的互相自动转换,例如StringInteger等。
  • 基本类型和其包装类型的互相转换
  • String和枚举类型之间的互相转换
结合lombok

在同时使用MapStructLombok的时候,需要注意编译顺序问题,当LombokSource Code 未生成时,生成的Mapper实现,将会缺少很多属性字段。

            <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也支持多个数据源同时映射一个对象。

//Goods.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods {
​
    private Integer id;
​
    private String name;
​
    private Long price;
​
}
​
//GoodsInfo.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GoodsInfo {
​
    private Integer id;
​
    private String sourceAddress;
​
    private LocalDate createTime;
​
    private String description;
}
//GoodsDto.java
@Data
public class GoodsDto {
​
    private Integer id;
​
    private Long price;
​
    private String goodsName;
​
    private String sourceAddress;
​
    private String description;
​
    private LocalDate createTime;
​
}
​
复制代码
@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的每个属性都映射到目标对象中。

更新已有实例
    @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一把梭,毕竟早点下班才是真谛~。

img

猜你喜欢

转载自juejin.im/post/7104586281601990687