00-MapStruct使用文档

发布于:2022-11-09 ⋅ 阅读:(10) ⋅ 点赞:(0) ⋅ 评论:(0)

目录

1. 引言

2. 设置

2.1 Maven

2.2 Gradle

2.3 配置项

2.4 MapStruct Intellij 插件

3. 定义Mapper

3.1 Source:原始类

3.2 Target:目标类

3.3 Mapper:映射类

3.4 MapStruct生成的实现类

3.5 测试

4. MapStruct整合Lombok

5. MapStruct使用案例


1. 引言

官网:https://mapstruct.org/

官方文档:https://mapstruct.org/documentation/reference-guide/

MapStruct是一个Java注解处理器,用于生成类型安全的Bean映射类。

我们只需要定义一个映射器接口,在接口中声明任何我们所需要的映射方法。MapStruct会在编译期为我们生成此接口的实现类。这个实现类使用简单的Java代码在源对象和目标对象之间进行映射,而不是使用反射或者类似的方式。

与手动编写映射代码相比,MapStruct通过生成繁琐且易于出错的代码来节省时间。

与动态映射框架相比,MapStruct具有以下优点:

  • 通过使用普通方法调用(setter/getter)而不是反射来快速执行
  • 编译时类型安全性:只能映射相互映射的对象和属性,不能将order实体意外映射到customer DTO等
  • 如果有如下问题,编译时可以报告出来:
    • 映射不完整(并非所有目标属性都被映射)
    • 映射不正确(找不到正确的映射方法或类型转换)

Talk is cheap,Show me the code

2. 设置

MapStruct主要包含两个jar包:

  • org.mapstruct:mapstruct:包含了我们所需要的注解,如:@Mapper
  • org.mapstruct:mapstruct-processor:包含了用于生成代码的注解处理器

一般情况下,我们只需要在项目中引入上面两个jar包就可以了。

下面分别介绍在Maven和Gradle中如何引入MapStruct。

2.1 Maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <!-- 其他配置-->
    
    <properties>
        <org.mapstruct.version>1.5.3.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.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>
    
    <!-- 其他配置-->
    
</project>

2.2 Gradle

...

ext {
    mapstructVersion = "1.5.3.Final"
}

dependencies {
    ...
    implementation "org.mapstruct:mapstruct:${mapstructVersion}"
    annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
    // If you are using mapstruct in test code
    testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
}

...

2.3 配置项

MapStruct为生成代码的过程提供了一些配置项,用于干预如何生成代码。

如果我们直接使用javac命令编译程序,则直接在命令行增加-Akey=value的参数进行配置。如:

javac xxx.jar -Amapstruct.suppressGeneratorTimestamp=true

Maven项目设置配置项的方式如下:

<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>
                <!-- due to problem in maven-compiler-plugin, for verbose mode add showWarnings -->
                <showWarnings>true</showWarnings>
                <compilerArgs>
                    <arg>
                        -Amapstruct.suppressGeneratorTimestamp=true
                    </arg>
                    <arg>
                        -Amapstruct.suppressGeneratorVersionInfoComment=true
                    </arg>
                    <arg>
                        -Amapstruct.verbose=true
                    </arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

Gradle项目设置配置项的方式如下:

...

compileJava {
    options.compilerArgs += [
        '-Amapstruct.suppressGeneratorTimestamp=true',
        '-Amapstruct.suppressGeneratorVersionInfoComment=true',
        '-Amapstruct.verbose=true'
    ]
}

...

所有的配置项及其作用可参考官方文档:配置项说明

2.4 MapStruct Intellij 插件

MapStruct Intellij 插件可以为我们使用MapStruct的过程中提供一些帮助。

  • Code completion in target, source, expression
  • Go To Declaration for properties in target and source
  • Find Usages of properties in target and source
  • Refactoring support
  • Errors and Quick Fixes

详情可参考:

3. 定义Mapper

代码结构如下:

3.1 Source:原始类

public class Car {

    private String make;
    private int numberOfSeats;
    private CarType type;

    // getter、setter 方法 略
    
    // toString 方法 略
    
}
public enum CarType {

    /**
     * 运动型
     */
    SPORT,

    /**
     * 其他
     */
    OTHER;
    
}

3.2 Target:目标类

public class CarDto {

    private String make;
    private int seatCount;
    private String type;

}

3.3 Mapper:映射类

/**
 * 定义的Mapper必须用@Mapper注解标注
 */
@Mapper
public interface CarMapper {

    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    /**
     * 1、相同名称和类型的字段,会自动映射,例如:make <br/>
     * 2、相同名称、不同类型的字段,如果类型支持隐式转换,也会自动映射,例如:type <br/>
     * 3、对应不同名称、不同类型的字段,则需要手动指定,例如:numberOfSeats -> seatCount <br/>
     *
     * @param car
     * @return
     */
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto car2Dto(Car car);

    /**
     * MapStruct会基于car2Dto方法,帮我们生成列表之间的转换方法
     * 
     * @param cars
     * @return
     */
    List<CarDto> car2Dtos(List<Car> cars);

    /**
     * 使用上面相反的配置进行映射,@InheritInverseConfiguration
     * 当有多个可用的映射规则时,可以通过指定name属性,表示使用哪个相反的配置,例如:@InheritInverseConfiguration(name = "car2Dto")
     * 
     * @param dto
     * @return
     */
    @InheritInverseConfiguration
    Car dto2Car(CarDto dto);

    List<Car> dto2Cars(List<CarDto> carDtos);

}

3.4 MapStruct生成的实现类

 

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2022-10-25T21:50:49+0800",
    comments = "version: 1.5.3.Final, compiler: javac, environment: Java 1.8.0_322 (Azul Systems, Inc.)"
)
public class CarMapperImpl implements CarMapper {

    @Override
    public CarDto car2Dto(Car car) {
        if ( car == null ) {
            return null;
        }

        CarDto carDto = new CarDto();

        carDto.setSeatCount( car.getNumberOfSeats() );
        carDto.setMake( car.getMake() );
        if ( car.getType() != null ) {
            carDto.setType( car.getType().name() );
        }

        return carDto;
    }

    @Override
    public List<CarDto> car2Dtos(List<Car> cars) {
        if ( cars == null ) {
            return null;
        }

        List<CarDto> list = new ArrayList<CarDto>( cars.size() );
        for ( Car car : cars ) {
            list.add( car2Dto( car ) );
        }

        return list;
    }

    @Override
    public Car dto2Car(CarDto dto) {
        if ( dto == null ) {
            return null;
        }

        Car car = new Car();

        car.setNumberOfSeats( dto.getSeatCount() );
        car.setMake( dto.getMake() );
        if ( dto.getType() != null ) {
            car.setType( Enum.valueOf( CarType.class, dto.getType() ) );
        }

        return car;
    }

    @Override
    public List<Car> dto2Cars(List<CarDto> carDtos) {
        if ( carDtos == null ) {
            return null;
        }

        List<Car> list = new ArrayList<Car>( carDtos.size() );
        for ( CarDto carDto : carDtos ) {
            list.add( dto2Car( carDto ) );
        }

        return list;
    }
}

3.5 测试

public class Main {

    public static void main(String[] args) {
        Car car = new Car();
        car.setMake("理想");
        car.setNumberOfSeats(6);
        car.setType(CarType.SPORT);
        System.out.println("Car: " + car);

        CarDto dto = CarMapper.INSTANCE.car2Dto(car);
        System.out.println("CarDto: " + dto);

        Car car2 = CarMapper.INSTANCE.dto2Car(dto);
        System.out.println("Car2: " + car2);
    }
    
}

4. MapStruct整合Lombok

众所周知,Lombok可以帮助我们为JavaBean,在编译期生成:构造方法、Getter、Setter、ToString等等方法。程序员往往都是懒惰的,因此,为了提升开发效率,Lombok得到了广泛的应用。

MapStruct框架的实现原理跟Lombok类似,也是在编译期生成类的转换代码。默认情况下,类的转换代码会调用JavaBean的getter、setter方法进行类之间的转换。

如果我们在项目中同时使用了MapStruct和Lombok,那么就需要严格控制MapStruct和Lombok的工作顺序。

那么如何来控制呢?具体可参考:

下文中的案例都会使用Lombok框架来为Java Bean生成getter、setter等方法。

5. MapStruct使用案例