
MapStruct 是一个用于生成类型安全,高性能,无依赖的bean映射代码的注释处理器
本文将介绍mapstruct的基本使用,并提供对应的示例
1. 简介
MapStruct是一个Java 注释处理器,用于生成类型安全的bean映射类。
使用时只需要定义一个mapper接口,在该接口声明所需的映射方法。在编译期间,MapStruct将生成此接口的实现类。
与动态映射框架相比,MapStruct具有以下优势:
快速,原理是生成普通方法调用而不是反射
编译时类型安全:只能映射相互映射的对象和属性
在构建时清除错误报告,如果
映射不完整(并非所有目标属性都已映射)
映射不正确(找不到合适的映射方法或类型转换)
2. 设置
MapStruct 包含以下组件
org.mapstruct:mapstruct:包含所需的注释,例如
@Mappingorg.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 基本映射
@Mapperpublic 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);}// 使用MapperCarDto carDto = CarMapper.INSTANCE.carToCarDTO(car);
接口也可以替换成抽象类,在抽象类中定义抽象方法
@Mapperpublic 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无法生成处理的逻辑,可以在接口中定义默认方法。
@Mapperpublic interface CarMapper {@Mapping(...)...CarDto carToCarDto(Car car);default PersonDto personToPersonDto(Person person) {//hand-written mapping logic}}
3.3 多个数据源参数的映射方法
主要解决将多个实体组合成一个数据传输对象的场景
@Mapperpublic 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(此时会返回更新后的对象)。
@Mapperpublic 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,可以通过它检索@Injectspring
:生成的映射器是一个单例范围的Spring bean,可以通过它检索@Autowiredjsr330
:生成的映射器使用{@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
可以指定理解的格式字符串
@Mapperpublic interface CarMapper {@Mapping(source = "power", numberFormat = "#.##E0")CarDto carToCarDto(Car car);}
java.util.Date
/XMLGregorianCalendar
和String
之间。java.text.SimpleDateFormat
可以通过dateFormat
选项指定理解的格式字符串
@Mapperpublic 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
注释将源枚举中的常量映射到具有其他名称的常量。
@Mapperpublic 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 CODEpublic class OrderMapperImpl implements OrderMapper {@Overridepublic 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
指示方法应继承相应反向方法的反向配置
@Mapperpublic 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;}@Overridepublic 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;@Overridepublic PersonDto personToPersonDto(Person person) {PersonDto dto = delegate.personToPersonDto( person );dto.setName( person.getFirstName() + " " + person.getLastName() );return dto;}}// 使用装饰器@Autowiredprivate 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/




