目录
1. 引言
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等方法。