MapStruct 是一个用于生成类型安全,高性能,无依赖的bean映射代码的注释处理器
本文将介绍mapstruct的基本使用,并提供对应的示例
1. 简介
MapStruct是一个Java 注释处理器,用于生成类型安全的bean映射类。
使用时只需要定义一个mapper接口,在该接口声明所需的映射方法。在编译期间,MapStruct将生成此接口的实现类。
与动态映射框架相比,MapStruct具有以下优势:
快速,原理是生成普通方法调用而不是反射
编译时类型安全:只能映射相互映射的对象和属性
在构建时清除错误报告,如果
映射不完整(并非所有目标属性都已映射)
映射不正确(找不到合适的映射方法或类型转换)
2. 设置
MapStruct 包含以下组件
org.mapstruct:mapstruct:包含所需的注释,例如
@Mapping
org.mapstruct:mapstruct-processor:包含生成映射器实现的注释处理器
2.1 Maven
MapStruct Maven配置
...
<properties>
<org.mapstruct.version>1.4.2.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>
...
2.2 配置选项
可以使用 注释处理器选项 配置MapStruct代码生成器。
MapStruct 配置选项示例
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.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>
<compilerArgs>
<compilerArg>
-Amapstruct.suppressGeneratorTimestamp=true
</compilerArg>
<compilerArg>
-Amapstruct.suppressGeneratorVersionInfoComment=true
</compilerArg>
</compilerArgs>
</configuration>
</plugin>
...
MapStruct 处理器配置选项
选项 | 目的 | 默认 |
---|---|---|
mapstruct. suppressGeneratorTimestamp | 如果设置为true ,则不会在生成的映射器类的注释中创建时间戳。 | false |
mapstruct. suppressGeneratorVersionInfoComment | 如果设置为true ,则不会在生成的映射器类 comment 中创建版本及编译器信息。 | false |
mapstruct.defaultComponentModel | 组件模型的名称(请参阅检索映射器)基于应生成的映射器。支持的值有:default 、 cdi 、 spring 、 jsr330 | default |
mapstruct.unmappedTargetPolicy | 如果未使用源值填充映射方法的目标对象的属性,则应用默认报告策略。支持的值是:ERROR :任何未映射的目标属性都将导致映射代码生成失败 WARN :任何未映射的目标属性都会在构建时发出警告 IGNORE :忽略未映射的目标属性如果为特定映射器via提供了策略 @Mapper#unmappedTargetPolicy() ,则注释中的值优先。 | WARN |
2.3 与Lombok一起使用
https://github.com/mapstruct/mapstruct-examples/blob/master/mapstruct-lombok/pom.xml
与Lombok一起使用Maven配置
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
<org.projectlombok.version>1.18.12</org.projectlombok.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<!-- lombok dependencies should not end up on classpath -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- IntelliJ pre 2018.1.1 requires the mapstruct processor to be present as provided dependency -->
<!-- <dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
<scope>provided</scope>
</dependency>-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.13.1</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<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>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
2.4 使用低版本
实际开发中,遇到 SpringBoot 1.5.7 RELEASE 版本的项目可以通过如下配置
低版本Maven配置
<properties>
<org.mapstruct.version>1.0.0.Final</org.mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>2.2.4</version>
<configuration>
<defaultOutputDirectory>
${project.build.directory}/generated-sources
</defaultOutputDirectory>
<processors>
<processor>org.mapstruct.ap.MappingProcessor</processor>
</processors>
</configuration>
<executions>
<execution>
<id>process</id>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
3. 定义映射器
3.1 基本映射
@Mapper
public interface CarMapper {
/**
* CarMapper 实例
*/
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
/**
* car转carDto
*
* @param car 汽车
* @return carDto
*/
@Mapping(source = "make", target = "manufacturer")
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDTO(Car car);
/**
* person 转 personDto
* @param person source
* @return personDto
*/
@Mapping(source = "name", target = "fullName")
PersonDto personToPersonDto(Person person);
}
// 使用Mapper
CarDto carDto = CarMapper.INSTANCE.carToCarDTO(car);
接口也可以替换成抽象类,在抽象类中定义抽象方法
@Mapper
public abstract class CarMapper {
/**
* CarMapper 实例
*/
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
/**
* car转carDto
*
* @param car 汽车
* @return carDto
*/
@Mapping(source = "make", target = "manufacturer")
@Mapping(source = "numberOfSeats", target = "seatCount")
public abstract CarDto carToCarDTO(Car car);
/**
* person 转 personDto
* @param person source
* @return personDto
*/
@Mapping(source = "name", target = "fullName")
public abstract PersonDto personToPersonDto(Person person);
}
3.2 添加自定义方法
如果我们遇到一些无法用MapStruct无法生成处理的逻辑,可以在接口中定义默认方法。
@Mapper
public interface CarMapper {
@Mapping(...)
...
CarDto carToCarDto(Car car);
default PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
3.3 多个数据源参数的映射方法
主要解决将多个实体组合成一个数据传输对象的场景
@Mapper
public interface AddressMapper {
@Mapping(source = "person.description", target = "description")
@Mapping(source = "address.houseNo", target = "houseNumber")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
3.4 更新现有bean实例
有些场景需要传入目标对象,而不是新建,可用通过为目标对象添加参数并使用 @MappingTarget
标记来实现。方法的返回值可以是void
,也可以是Car(此时会返回更新后的对象)。
@Mapper
public interface CarMapper {
void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}
4. 检索映射器
4.1 Mappers工厂
CarMapper mapper = Mappers.getMapper( CarMapper.class );
4.2 依赖注入
@Mapper(componentModel = "spring")
public interface CarMapper {
CarDto carToCarDto(Car car);
}
组件模型:
default
:映射器不使用组件模型,通常通过检索实例Mappers#getMapper(Class)
cdi
:生成的映射器是一个应用程序范围的CDI bean,可以通过它检索@Inject
spring
:生成的映射器是一个单例范围的Spring bean,可以通过它检索@Autowired
jsr330
:生成的映射器使用{@code @Named}进行注释,并且可以通过@Inject
(例如使用Spring)进行检索
5. 数据类型转换
5.1 隐式类型转换
在许多情况下,MapStruct会自动处理类型转换。
Java基本数据类型及其相应的包装类型,例如之间
int
和Integer
,boolean
和Boolean
所有Java基本类型之间(包括其包装)和
String
之间在
enum
类型和之间String
在大数字类型(
java.math.BigInteger
,java.math.BigDecimal
)和Java原始类型(包括它们的包装器)以及String之间。java.text.DecimalFormat
可以指定理解的格式字符串
@Mapper
public interface CarMapper {
@Mapping(source = "power", numberFormat = "#.##E0")
CarDto carToCarDto(Car car);
}
java.util.Date
/XMLGregorianCalendar
和String
之间。java.text.SimpleDateFormat
可以通过dateFormat
选项指定理解的格式字符串
@Mapper
public interface CarMapper {
@Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
CarDto carToCarDto(Car car);
@IterableMapping(dateFormat = "dd.MM.yyyy")
List<String> stringListToDateList(List<Date> dates);
}
5.2 调用其他映射器
除了在同一映射器类型上定义的方法之外,MapStruct还可以调用其他类中定义的映射方法,无论是MapStruct生成的映射器还是手写映射方法。
public class DateMapper {
public String asString(Date date) {
return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
.format( date ) : null;
}
public Date asDate(String date) {
try {
return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
.parse( date ) : null;
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
}
}
// 引用另一个映射器
@Mapper(uses=DateMapper.class)
public class CarMapper {
CarDto carToCarDto(Car car);
}
6. 其他映射
映射也支持集合 和 流 作为入参,这里暂不总结
6.1 映射枚举类型
默认情况下,源枚举中的每个常量都映射到目标枚举类型中具有相同名称的常量。如果需要,可以借助@ValueMapping
注释将源枚举中的常量映射到具有其他名称的常量。
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );
@ValueMappings({
@ValueMapping(source = "EXTRA", target = "SPECIAL"),
@ValueMapping(source = "STANDARD", target = "DEFAULT"),
@ValueMapping(source = "NORMAL", target = "DEFAULT")
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
生成的代码
// GENERATED CODE
public class OrderMapperImpl implements OrderMapper {
@Override
public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
if ( orderType == null ) {
return null;
}
ExternalOrderType externalOrderType_;
switch ( orderType ) {
case EXTRA: externalOrderType_ = ExternalOrderType.SPECIAL;
break;
case STANDARD: externalOrderType_ = ExternalOrderType.DEFAULT;
break;
case NORMAL: externalOrderType_ = ExternalOrderType.DEFAULT;
break;
case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL;
break;
case B2B: externalOrderType_ = ExternalOrderType.B2B;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType );
}
return externalOrderType_;
}
}
7. 高级映射选项
7.1 默认值和常量
@Mapper(uses = StringListMapper.class)
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
@Mapping(target = "longProperty", source = "longProp", defaultValue = "-1")
@Mapping(target = "stringConstant", constant = "Constant Value")
@Mapping(target = "integerConstant", constant = "14")
@Mapping(target = "longWrapperConstant", constant = "3001")
@Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014")
@Mapping(target = "stringListConstants", constant = "jack-jill-tom")
Target sourceToTarget(Source s);
}
defaultValue
是String
类型,其中包含了隐式转换如果是日期或者数字,需要指定对应的
dateFormat
或numberFormat
常量
"jack-jill-tom"
演示了如何调用手写类StringListMapper
来将以破折号分隔的列表映射到aList<String>
7.2 表达式和默认表达式
目前只支持Java作为语言。可以在@Mapper
中加入 imports
导入对应的 package
使用表达式示例
imports org.sample.TimeAndFormat;
@Mapper( imports = TimeAndFormat.class )
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "timeAndFormat",
expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )")
Target sourceToTarget(Source s);
}
使用默认表达式示例
imports java.util.UUID;
@Mapper( imports = UUID.class )
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
// 如果sourceId为null,则生成UUID
@Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )")
Target sourceToTarget(Source s);
}
7.3 确定结果类型
当结果类型具有继承关系时,可通过以下方式解决
@Mapper( uses = FruitFactory.class )
public interface FruitMapper {
@BeanMapping( resultType = Apple.class )
Fruit map( FruitDto source );
}
// 使用的自定义类
public class FruitFactory {
public Apple createApple() {
return new Apple( "Apple" );
}
public Banana createBanana() {
return new Banana( "Banana" );
}
}
7.4 关于 null 参数的映射结果
可以指定不同的策略,这里使用到了再来总结
7.5 异常的处理
如果使用的自定义映射类中方法定义了异常
@Mapper(uses = HandWritten.class)
public interface CarMapper {
CarDto carToCarDto(Car car) throws GearException;
}
// HandWritten 中自定义的方法,抛出异常
public class HandWritten {
private static final String[] GEAR = {"ONE", "TWO", "THREE", "OVERDRIVE", "REVERSE"};
public String toGear(Integer gear) throws GearException, FatalException {
if ( gear == null ) {
throw new FatalException("null is not a valid gear");
}
if ( gear < 0 && gear > GEAR.length ) {
throw new GearException("invalid gear");
}
return GEAR[gear];
}
}
8. 重用映射配置
8.1 逆映射
使用注释@InheritInverseConfiguration
指示方法应继承相应反向方法的反向配置
@Mapper
public interface CarMapper {
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToDto(Car car);
@InheritInverseConfiguration
@Mapping(target = "numberOfSeats", ignore = true)
Car carDtoToCar(CarDto carDto);
}
9. 自定义映射
9.1 使用装饰器映射自定义
在某些情况下,可能需要定制生成的映射方法,例如,在目标对象中设置不能由生成的方法实现设置的附加属性。MapStruct使用装饰器支持此要求。
// 应用装饰器
@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);
}
// 装饰器必须是装饰映射器类型的子类型
public abstract class PersonMapperDecorator implements PersonMapper {
private final PersonMapper delegate;
public PersonMapperDecorator(PersonMapper delegate) {
this.delegate = delegate;
}
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setFullName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
如果使用 componentModel="spring" 的装饰器, 扩展装饰器的生成类, 会使用Spring的@Primary
注释进行注释
public abstract class PersonMapperDecorator implements PersonMapper {
@Autowired
@Qualifier("delegate")
private PersonMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
// 使用装饰器
@Autowired
private PersonMapper personMapper;
参考文章
github:
Github https://github.com/mapstruct/mapstruct/
示例 https://github.com/mapstruct/mapstruct-examples
官方指南(中文):http://www.kailing.pub/MapStruct1.3/index.html
官方指南:https://mapstruct.org/documentation/stable/reference/html/