添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

前言

因项目的DTO越来越多,且不同层级的DTO和javabean需要转换,大量使用bean.copy方法,造成性能下降,阅读性差的问题,准备引入MapStruct进行javabean数据的映射转换。

MapStruct是什么

MapStruct是一个Java注释处理器,用于为Java bean类生成类型安全和高性能的映射器。它使您不必手工编写映射代码,这是一个繁琐且容易出错的任务。该生成器具有合理的默认值和许多内置的类型转换,但是在配置或实现特殊行为时,它会自动退出。

  • 通过使用普通方法调用而不是反射来快速执行
  • 编译时类型安全。只能映射彼此映射的对象和属性,因此不会将实体意外映射到客户DTO等等。
  • 独立代码-没有运行时依赖
  • 在构建时清除错误报告,如:
    a. 映射不完整(并非所有目标属性都被映射)
    b. 映射不正确(找不到正确的映射方法或类型转换)
  • 易于调试的映射代码(或可以手动编辑,例如,如果生成器中有错误)
  • 运行环境

    这里使用的是MapStruct 1.3.1版本,需要Java 1.8或以上(1.2.x版本好像不支持Java 1.8,需要其他包代替)

    简单说明

    需要在两种类型之间创建映射,请声明一个映射器类,如下所示:

    1
    2
    3
    4
    5
    6
    @Mapper
    public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car);
    }

    在编译时,MapStruct将生成此接口的实现。生成的实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即不涉及反射。默认情况下,如果属性在源和目标中具有相同的名称,则将映射它们,但是您可以使用@Mapping和少量其他注释来控制此属性和许多其他方面。

    开发工具插件

    IntelliJ idea 中可在插件市场中下载使用插件( https://plugins.jetbrains.com/plugin/10036-mapstruct-support
    PS:这里用到Lombok,因此也需要下载idea插件市场的一个Lombok插件

    确保您idea版本至少为 2018.2.x(由于对maven-compiler-plugin的注解处理器的支持来自该版本,因此是必需的)。
    在IntelliJ中启用注释处理(Build, Execution, Deployment -> Compiler -> Annotation Processors)

    Maven环境配置

    对于基于Maven的项目,将以下内容添加到您的POM文件中以使用MapStruct(依赖关系在Maven Central中可用):

    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
    ...
    <properties>
    <org.mapstruct.version>1.3.1.Final</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.0</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>

    <!--添加Lombok的注册处理器-->
    <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
    </path>
    </annotationProcessorPaths>
    </configuration>
    </plugin>
    </plugins>
    </build>
    ...

    碰到的问题

    当MapStruct和Lombok混合使用时,会出现异常,找不到字段:
    Warning:java: 来自注释处理程序 'net.java.dev.hickory.prism.internal.PrismGenerator' 的受支持 source 版本 'RELEASE_6' 低于 -source '1.8'
    Error:(101, 16) java: No property named "numberOfSeats" exists in source parameter(s). Did you mean "null"?

    解决方法只需要在<maven-compiler-plugin>节点的<annotationProcessorPaths>中添加:

    1
    2
    3
    4
    5
    6
    <!--添加Lombok的注册处理器-->
    <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
    </path>

    下面是代码

    数据来源实体,数据库实体(this) 转换到 DTO CarDto 转换目标实体,DTO 转换到 响应实体(this) 映射interface CarMapper 定义用于转换映射的interface,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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    package raven.spring.study;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.Getter;
    import lombok.ToString;
    import org.junit.jupiter.api.Test;
    import org.mapstruct.Mapper;
    import org.mapstruct.Mapping;
    import org.mapstruct.factory.Mappers;

    class MapStructTests {

    @Test
    public void shouldMapCarToDtoTest() {

    // 实例化数据对象
    Car car = new Car( "Morris", 5, CarType.SEDAN ,200.19);

    // 获得映射实例, 并调用转换方法
    CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);

    System.out.println(car);
    System.out.println(carDto);

    System.out.println("同类型同名:" + carDto.getMake().equals("Morris"));
    System.out.println("同类型非同名" + (carDto.getSeatCount() == (5)));
    System.out.println("字符串-枚举:" + carDto.getType().equals("SEDAN"));
    System.out.println("非同类型非同名:" + carDto.getMaxCarSpeed().equals(String.valueOf(car.getMaxSpeed())));

    /*
    打印结果:
    Car{make='Morris', numberOfSeats=5, type=CarType{key='SEDAN'}, maxSpeed=200.19}
    CarDto{make='Morris', seatCount=5, type='SEDAN', maxCarSpeed='200.19'}
    同类型同名:true
    同类型非同名true
    字符串-枚举:true
    非同类型非同名:true
    */

    }

    }

    // 数据实体->DTO 两种类型非常相似,只有座位数属性具有不同的名称,并且类型属性在Car类中具有特殊的枚举类型,但在DTO中是纯字符串。

    /**
    * 数据实体
    */

    @Data
    @AllArgsConstructor
    @ToString
    class Car {

    private String make;
    private int numberOfSeats;
    private CarType type;
    private double maxSpeed;
    }

    /**
    * 传输DTO
    */

    @Data
    @ToString
    class CarDto {

    private String make;
    private int seatCount;
    private String type;
    private String maxCarSpeed;
    }


    /**
    * 枚举
    */

    @AllArgsConstructor
    @Getter
    enum CarType {
    SEDAN("SEDAN");
    private String key;
    }

    /**
    * 映射器
    * 从Car对象中创建CarDto对象,需要定义一个映射器接口
    */

    // @Mapper将接口标记为映射接口,并让MapStruct处理器在编译期间启动。
    // 对于源对象和目标对象中名称不同的属性,@Mapping可用于配置名称。
    // 当然,一个接口中可以有多种映射方法,MapStruct将为所有接口生成一个实现。
    @Mapper
    interface CarMapper {

    // 可以从Mappers类检索接口实现的实例。按照约定,该接口声明一个成员INSTANCE,为客户端提供对映射器实现的访问。
    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );

    // 实际的映射方法将源对象作为参数并返回目标对象。其名称可以自由选择。
    // 在必要和可能的情况下,将对源和目标中具有不同类型的属性执行类型转换,例如type属性将从枚举类型转换为字符串。
    @Mapping(source = "numberOfSeats", target = "seatCount")
    @Mapping(source = "maxSpeed", target = "maxCarSpeed")
    CarDto carToCarDto(Car car);
    }

    总结

    使用起来还是挺方便的,只需要新建一个映射器即可使用,不同的类型或者字段也能标记映射,阅读性更高,维护性也高。