If set to true
, the creation of a time stamp in the @Generated
annotation in the generated mapper classes is suppressed.
MapStruct 1.4.2.Final Reference Guide
Gunnar Morling, Andreas Gudian, Sjaak Derksen, Filip Hrisafov and the MapStruct community2021-01-31
Translated by RiceMarch [email protected],[email protected] 由白云苍饭进行翻译
这是MapStruct的参考文档,MapStruct是一个用于生成类型安全、高性能和无依赖的bean映射代码的注解处理器。本指南涵盖了MapStruct提供的所有功能。如果本指南没有回答你所有的问题,你可以加入MapStruct的Google群组来获得帮助。
如果您在本指南中发现了任何错误或者用词不当,请在 MapStruct GitHub 仓库中打开一个问题与我们取得联系,或者通过发送一个拉取请求来修复它。
本文采用Creative Commons Attribution-ShareAlike 4.0 International License.进行许可。
MapStruct 是一款用于用于生成类型安全的Bean映射类的注解器。
您所要做的就是定义一个映射器接口,在该接口上声明您所需的所有映射方法。通过编译,MapStruct将会生成该接口的实现。该接口的实现使用朴素的Java方法来实现源对象和目标对象之间的映射,即没有反射等情况的存在
与手写映射代码相比,MapStruct可以节省时间,而手写映射代码则过于繁琐且容易出错。通过遵循约定俗成的配置方式,MapStruct使用默认值进行大部分映射,但在配置或实现特殊行为时,MapStruct同样可以实现自由编写。
与动态映射框架相比,MapStruct具有以下优点:
通过使用普通方法调用代替反射来快速执行。
编译时类型安全:只有指明的对象和属性可以相互映射,不会出现一个order entity 被映射为customer DTO 等情况。
在build时即可清除映射错误:
MapStruct是一个基于JSR 269的Java注释处理器,因此可以在命令行构建(javac、Ant、Maven等)以及IDE中使用。
它包含以下Artifacts:
@Mapping
对于基于Maven的项目,请在你的POM文件中添加以下内容
Example 1. Maven configuration
...
<properties>
<org.mapstruct.version>1.4.2.Finalorg.mapstruct.version>
properties>
...
<dependencies>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstructartifactId>
<version>${org.mapstruct.version}version>
dependency>
dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>${org.mapstruct.version}version>
path>
annotationProcessorPaths>
configuration>
plugin>
plugins>
build>
...
Tips:
If you are working with the Eclipse IDE, make sure to have a current version of the M2E plug-in. When importing a Maven project configured as shown above, it will set up the MapStruct annotation processor so it runs right in the IDE, whenever you save a mapper type. Neat, isn’t it?
To double check that everything is working as expected, go to your project’s properties and select “Java Compiler” → “Annotation Processing” → “Factory Path”. The MapStruct processor JAR should be listed and enabled there. Any processor options configured via the compiler plug-in (see below) should be listed under “Java Compiler” → “Annotation Processing”.
If the processor is not kicking in, check that the configuration of annotation processors through M2E is enabled. To do so, go to “Preferences” → “Maven” → “Annotation Processing” and select “Automatically configure JDT APT”. Alternatively, specify the following in the
properties
section of your POM file:.
jdt_apt Also make sure that your project is using Java 1.8 or later (project properties → “Java Compiler” → “Compile Compliance Level”). It will not work with older versions.
添加以下内容到你的Gradle构建文件中
Example 2. Gradle configuration
...
plugins {
...
id "com.diffplug.eclipse.apt" version "3.26.0" // Only for Eclipse
}
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}"
}
...
你可以在GitHub上的mapstruct-examples项目中找到一个完整的例子。
在您的 build.xml 文件中添加配置如下的 javac 任务,以便在您的基于 Ant 的项目中启用 MapStruct。根据项目布局的需要调整路径。
Example 3. Ant configuration
...
<javac
srcdir="src/main/java"
destdir="target/classes"
classpath="path/to/mapstruct-1.4.2.Final.jar">
<compilerarg line="-processorpath path/to/mapstruct-processor-1.4.2.Final.jar"/>
<compilerarg line="-s target/generated-sources"/>
javac>
...
你可以在GitHub上的mapstruct-examples项目中找到一个完整的例子。
MapStruct代码生成器可以使用注释选项进行配置。
当直接通过javac
调用,这些选项以-Akey=value
的形式传递给编译器(compiler)。当通过Maven使用时,任何处理器选项都可以通过Maven处理器插件配置中的options元素来传递,如:
Example 4. Maven configuration
...
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.5.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>${org.mapstruct.version}version>
path>
annotationProcessorPaths>
<showWarnings>trueshowWarnings>
<compilerArgs>
<arg>
-Amapstruct.suppressGeneratorTimestamp=true
arg>
<arg>
-Amapstruct.suppressGeneratorVersionInfoComment=true
arg>
<arg>
-Amapstruct.verbose=true
arg>
compilerArgs>
configuration>
plugin>
...
Example 5. Gradle configuration
...
compileJava {
options.compilerArgs += [
'-Amapstruct.suppressGeneratorTimestamp=true',
'-Amapstruct.suppressGeneratorVersionInfoComment=true',
'-Amapstruct.verbose=true'
]
}
...
有以下选择:
The following options exist:
Table 1. MapStruct processor options
选项/Option | 作用/Purpose | 默认值/Default |
---|---|---|
|
If set to |
|
|
If set to |
|
|
If set to |
|
|
The name of the component model (see Retrieving a mapper) based on which mappers should be generated. Supported values are:
If a component model is given for a specific mapper via |
|
|
The type of the injection in mapper via parameter Supported values are:
When CDI |
|
|
The default reporting policy to be applied in case an attribute of the target object of a mapping method is not populated with a source value. Supported values are:
If a policy is given for a specific mapper via |
|
MapStruct实验性支持Java 9(JPMS)。
Java 9 带来的一个核心变化是JDK的模块化。其带来的影响是为了使用javax.annotation.Generated annotation
,需要为一个项目启用特定的模块。@Generated
已被Mapstruct添加在生成映射类中,用于将其标记为生成的代码,说明生成日期、生成器版本等。
为了使用@Generated
注解,必须启用java.xml.ws.annotation
模块。使用Maven时,可以这样做。
export MAVEN_OPTS="--add-modules java.xml.ws.annotation"
如果@Generated
注解不生效,MapStruct会检测到这种情况,不会将其添加到生成的映射器中。
In Java 9
java.annotation.processing.Generated
was added (part of thejava.compiler
module), if this annotation is available then it will be used.
在本节中,您将了解如何使用MapStruct定义bean mapper,以及有哪些选项可以进行操作。
Example 6. Java interface to define a mapper
@Mapper
public interface CarMapper {
@Mapping(source = "make", target = "manufacturer")
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car);
@Mapping(source = "name", target = "fullName")
PersonDto personToPersonDto(Person person);
}
通过 @Mapper
注解, MapStruct代码生成器在代码build时创建CarMapper
接口的实现。
在生成的方法实现中,源类型(如Car
)的所有可读属性将被复制到目标类型(如CarDto
)的相应属性中。
@Mapping
注解来指定。Tips.1
The property name as defined in the JavaBeans specification must be specified in the
@Mapping
annotation, e.g. seatCount for a property with the accessor methodsgetSeatCount()
andsetSeatCount()
.
Tips.2
By means of the
@BeanMapping(ignoreByDefault = true)
the default behavior will be explicit mapping, meaning that all mappings have to be specified by means of the@Mapping
and no warnings will be issued on missing target properties.
Tips.3
Fluent setters are also supported. Fluent setters are setters that return the same type as the type being modified.
E.g.
public Builder seatCount(int seatCount) { this.seatCount = seatCount; return this; }
为了更好的理解MapStruct的功能,我们来看由MapStruct生成的carToCarDto()
的具体实现
Example 7. Code generated by MapStruct
// GENERATED CODE
public class CarMapperImpl implements CarMapper {
@Override
public CarDto carToCarDto(Car car) {
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
if ( car.getFeatures() != null ) {
carDto.setFeatures( new ArrayList<String>( car.getFeatures() ) );
}
carDto.setManufacturer( car.getMake() );
carDto.setSeatCount( car.getNumberOfSeats() );
carDto.setDriver( personToPersonDto( car.getDriver() ) );
carDto.setPrice( String.valueOf( car.getPrice() ) );
if ( car.getCategory() != null ) {
carDto.setCategory( car.getCategory().toString() );
}
carDto.setEngine( engineToEngineDto( car.getEngine() ) );
return carDto;
}
@Override
public PersonDto personToPersonDto(Person person) {
//...
}
private EngineDto engineToEngineDto(Engine engine) {
if ( engine == null ) {
return null;
}
EngineDto engineDto = new EngineDto();
engineDto.setHorsePower(engine.getHorsePower());
engineDto.setFuel(engine.getFuel());
return engineDto;
}
}
MapStruct 的生成理念是生成尽可能多的像你自己亲手写的的代码,这正意味着值是通过普通的getter/setter调用而不是反射或其他类似的方式从源复制到目标。
如示例所示,生成的代码会考虑到任何通过@Mapping指定的名称映射。如果映射属性的类型在源实体和目标实体中是不同的,MapStruct 将通过自动转换(例如,对于价格属性,请参见隐式类型转换)或选择调用/创建另一个映射方法(例如,对于驱动程序/引擎属性,请参见 映射对象引用)。
MapStruct支持使用元注解,现在@Mapping
注解支持ElementType#METHOD
以及使用ElementType#ANNOTATION_TYPE
的@Target
。这使得@Mapping
可以用于其他(用户定义的)注解,以达到重复使用的目的。
@Retention(RetentionPolicy.CLASS)
@Mapping(target = "id", ignore = true)
@Mapping(target = "creationDate", expression = "java(new java.util.Date())")
@Mapping(target = "name", source = "groupName")
public @interface ToEntity {
}
此举可以在不需要有一个共同的基础类型的情况下描述一个Entity
。举个栗子,在下面的StorageMapper
中,ShelveEntity
和BoxEntity
并不共享一个共同的基础类型。
@Mapper
public interface StorageMapper {
StorageMapper INSTANCE = Mappers.getMapper( StorageMapper.class );
@ToEntity
@Mapping( target = "weightLimit", source = "maxWeight")
ShelveEntity map(ShelveDto source);
@ToEntity
@Mapping( target = "label", source = "designation")
BoxEntity map(BoxDto source);
}
然而,他们有一些相同的属性,@ToEntity
注解假设这些目标Beans ShelveEntity
及 BoxEntity
有以下属性: "id"
, "creationDate"
和 "name"
》此外,它还假设源Bean ShelveDto
和BoxDto
总是有一个属性 "groupName"
。这个概念也被称为 “duck-typing”。换句话说,如果它的叫声像鸭子,走路像鸭子,它可能是一只鸭子。只关心行为,不关心类型
这个功能还是实验性的。错误信息还不成熟:显示发生问题的方法,以及@Mapping注解中的相关值。然而,组成方面是不可见的。这些信息 “就像”@Mapping会直接出现在相关方法上一样。因此,用户应该谨慎使用这个功能,特别是当不确定某个属性是否始终存在时。
一个更安全(但也更啰嗦)的方法是在目标Bean和源Bean上定义基类/接口,然后使用@InheritConfiguration
来实现同样的结果。(有关 映射配置继承,Mapping configuration inheritance)
在一些场景中,可能会需要手动实现一个从一种类型到另一种类型的特定的mapping,这种映射不能由MapStruct自动生成。 处理这个问题的一种方法是在另一个类上实现自定义方法,然后由MapStruct生成的映射器使用。
另外,当使用Java 8或更高版本时,你可以直接在mapper接口中实现自定义方法作为默认方法。如果参数和返回类型匹配,生成的代码将调用默认方法。
@Mapper
public interface CarMapper {
@Mapping(...)
...
CarDto carToCarDto(Car car);
default PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
MapStruct生成的类实现了carToCarDto()
方法。carToCarDto()
中生成的代码将在映射driver
属性时调用手动实现的personToPersonDto()
方法。
mapper的定义可以不使用interface而使用抽象类的形式,并直接在映射器类中实现自定义方法。在这种情况下,MapStruct将会生成包含所有抽象方法的实现的抽象类的扩展。与声明默认方法相比,这种方法的一个优点是可以在mapper类中声明额外的字段。
之前的例子中,从Person到PersonDto的映射需要一些特殊的逻辑,那么可以这样定义。
Example 9. Mapper defined by an abstract class
@Mapper
public abstract class CarMapper {
@Mapping(...)
...
public abstract CarDto carToCarDto(Car car);
public PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
MapStruct将会生成一个包含抽象方法carToCarDto()
实现的抽象类CarMapper
的子类。
carToCarDto()
中生成的代码在映射driver
属性时,会调用手动实现的personToPersonDto()
方法。
MapStruct还支持带有多个源参数的映射方法。这是一种很给力的方法,例如,为了将几个实体(entities)合并到一个数据传输对象(data transfer object)中。下面是一个例子。
Example 10. Mapping method with several source parameters
@Mapper
public interface AddressMapper {
@Mapping(source = "person.description", target = "description")
@Mapping(source = "address.houseNo", target = "houseNumber")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
上午所示的mapping方法需要两个源参数,并返回一个组合的目标对象。与单参数mapping方法一样,属性是通过名称进行映射的。
如果多个源对象定义了具有相同名称的属性,则必须使用 @Mapping
注解指定用于检索属性的源参数,如示例中描述属性所示。如果这样的歧义没有被解决则将会产生error
,对于在给定的source object
中只存在一次的属性,可以不指定source object
的名称,因为它可以自动确定。
Tips.1
Specifying the parameter in which the property resides is mandatory when using the @Mapping annotation.
Tips.2
Mapping methods with several source parameters will return null in case all the source parameters are null. Otherwise the target object will be instantiated and all properties from the provided parameters will be propagated.
MapStruct还提供了直接引用源参数的方式。
Example 11. Mapping method directly referring to a source parameter
@Mapper
public interface AddressMapper {
@Mapping(source = "person.description", target = "description")
@Mapping(source = "hn", target = "houseNumber")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}
在这种情况下,源参数直接被映射到目标中,如上例所示。参数hn
,一个非bean类型(在本例中是java.lang.Integer
)被映射到houseNumber
。
如果你不想显式命名嵌套的源Bean的所有属性,你可以使用.
作为目标。这将告诉MapStruct将源Bean中的所有属性映射到目标对象。下面是一个例子。
Example 12. use of “target this” annotation “.”
@Mapper
public interface CustomerMapper {
@Mapping( target = "name", source = "record.name" )
@Mapping( target = ".", source = "record" )
@Mapping( target = ".", source = "account" )
Customer customerDtoToCustomer(CustomerDto customerDto);
}
生成的代码会将CustomerDto.record
中的每一个属性直接映射到Customer
中,而不需要手动命名其中的任何一个。Customer.account
也是如此。
当嵌套映射有冲突时,可以通过明确定义映射来解决。例如在上面的例子中。name
同时出现在CustomerDto.record
和CustomerDto.account
中。映射@Mapping( target = "name", source = "record.name")
可以解决这个冲突。
这种 "target this "的标记方法在将分层对象映射到平面对象时非常有用,反之亦然(@InheritInverseConfiguration
)。
在某些情况下,您需要的映射不是创建一个新的目标类型实例,而是更新该类型的现有实例。这种映射可以通过为目标对象添加一个参数,并用@MappingTarget标记这个参数来实现。下面是一个例子。
Example 13. Update method
@Mapper
public interface CarMapper {
void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}
updateCarFromDto()方法生成的代码将用给定CarDto对象的属性更新传递的Car实例。标记为映射目标的参数可能只有一个。你也可以将该方法的返回类型设置为目标参数的类型,而不是void
,在此情况下会生成的实现更新传递的映射目标并将其也返回。它同样允许流式调用mapping方法(fluent invocations of mapping methods.)
对于CollectionMappingStrategy.ACCESSOR_ONLY
目标bean的集合或Map类型的属性将被清除,然后用相应的源集合或地图的值填充。除此以外,对于CollectionMappingStrategy.ADDER_PREERRFED
或CollectionMappingStrategy.TARGET_IMMUTABLE
目标将不会被清除,而会立即填充值。
MapStruct 同样支持映射没有getter/setter
方法的public
字段。MapStruct如果找不到合适的属性的getter/setter方法,就会把字段作为读/写访问器。
如果一个字段是public
或 public final
,则被视为读访问器。如果一个字段是static
的,则不被视为读存取器。
只有当一个字段是public
时,它才被认为是一个写访问器。如果一个字段是 final
和/或static
,则不被视为写访问器。
简单的例子:
Example 14. Example classes for mapping
public class Customer {
private Long id;
private String name;
//getters and setter omitted for brevity
}
public class CustomerDto {
public Long id;
public String customerName;
}
@Mapper
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );
@Mapping(source = "customerName", target = "name")
Customer toCustomer(CustomerDto customerDto);
@InheritInverseConfiguration
CustomerDto fromCustomer(Customer customer);
}
对于上面的配置,生成的mapper 如下:
Example 15. Generated mapper for example classes
// GENERATED CODE
public class CustomerMapperImpl implements CustomerMapper {
@Override
public Customer toCustomer(CustomerDto customerDto) {
// ...
customer.setId( customerDto.id );
customer.setName( customerDto.customerName );
// ...
}
@Override
public CustomerDto fromCustomer(Customer customer) {
// ...
customerDto.id = customer.getId();
customerDto.customerName = customer.getName();
// ...
}
}
你可以找到更完整的项目例子在 mapstruct-examples-field-mapping Github网站中
MapStruct 也支持通过builders映射不可变类型,当执行映射时,MapStruct会检查是否有被映射类型的builder。这是通过 BuilderProvider SPI
完成的。如果某个类型存在Builder,那么该Builder将被用于映射。
BuilderProvider
的默认实现假设如下:
Person
有一个public static 方法,返回PersonBuilder
。PersonBuilder
有一个返回Person
的方法。build
的方法,如果存在这样的方法,那么就会使用这个方法,否则就会产生一个编译错误。@Builder
在@BeanMapping
、@Mapper
或@MapperConfig
中定义特定的build方法。DefaultBuilderProvider SPI
将抛出MoreThanOneBuilderCreationMethodException
。如果出现MoreThanOneBuilderCreationMethodException
,MapStruct将在编译中写一个警告,并且不使用任何构建器。如果找到了这样的类型,那么MapStruct将使用该类型来执行映射到(即它将寻找进入该类型的setter)。为了完成映射,MapStruct会生成代码,调用构建器的构建方法。
Tips.1
Builder detection can be switched off by means of @Builder#disableBuilder. MapStruct will fall back on regular getters / setters in case builders are disabled.
Tips.2
The Object factories are also considered for the builder type. E.g. If an object factory exists for our PersonBuilder then this factory would be used instead of the builder creation method.
Example 16. Person with Builder example
public class Person {
private final String name;
protected Person(Person.Builder builder) {
this.name = builder.name;
}
public static Person.Builder builder() {
return new Person.Builder();
}
public static class Builder {
private String name;
public Builder name(String name) {
this.name = name;
return this;
}
public Person create() {
return new Person( this );
}
}
}
Example 17. Person Mapper definition
public interface PersonMapper {
Person map(PersonDto dto);
}
Example 18. Generated mapper with builder
// GENERATED CODE
public class PersonMapperImpl implements PersonMapper {
public Person map(PersonDto dto) {
if (dto == null) {
return null;
}
Person.Builder builder = Person.builder();
builder.name( dto.getName() );
return builder.create();
}
}
支持生成器框架:
ImmutablesAccessorNamingStrategy
和ImmutablesBuilderProvider
。FreeBuilderAccessorNamingStrategy
。当使用FreeBuilder时,那么应该遵循JavaBean惯例,否则MapStruct将无法识别流畅的获取器。BuilderProvider
的定义规则,那么对于自定义的构建器(手写的)也是有效的。否则,你需要写一个自定义的BuilderProvider
。Tips
In case you want to disable using builders then you can use the
NoOpBuilderProvider
by creating aorg.mapstruct.ap.spi.BuilderProvider
file in theMETA-INF/services
directory withorg.mapstruct.ap.spi.NoOpBuilderProvider
as it’s content.
MapStruct支持使用constructors生成目标类型。在进行映射时,MapStruct 会检查是否有被映射类型的构建器。如果没有构建器,那么MapStruct会寻找一个可访问的构造函数。当有多个构造函数时,则按以下方法选择应该使用的构造函数。
@Default
(来自任何包,请看 Non-shipped annotations) 那么它将被使用。@Default
注解 (来自任何包,参见 Non-shipped annotations注解)Example 19. Deciding which constructor to use
public class Vehicle {
protected Vehicle() {
}
// MapStruct will use this constructor, because it is a single public constructor
public Vehicle(String color) {
}
}
public class Car {
// MapStruct will use this constructor, because it is a parameterless empty constructor
public Car() {
}
public Car(String make, String color) {
}
}
public class Truck {
public Truck() {
}
// MapStruct will use this constructor, because it is annotated with @Default
@Default
public Truck(String make, String color) {
}
}
public class Van {
// There will be a compilation error when using this class because MapStruct cannot pick a constructor
public Van(String make) {
}
public Van(String make, String color) {
}
}
当使用构造函数时,那么将使用构造函数的参数名称并与目标属性相匹配。当构造函数有一个名为@ConstructorProperties
的注解时(来自任何包,请看 Non-shipped annotations),那么这个注解将被用来获取参数的名称。
Tips
When an object factory method or a method annotated with
@ObjectFactory
exists, it will take precedence over any constructor defined in the target. The target object constructor will not be used in that case.
Example 20. Person with constructor parameters
public class Person {
private final String name;
private final String surname;
public Person(String name, String surname) {
this.name = name;
this.surname = surname;
}
}
Example 21. Person With Constructor Mapper definition
public interface PersonMapper {
Person map(PersonDto dto);
}
Example 22. Generated mapper with constructor
// GENERATED CODE
public class PersonMapperImpl implements PersonMapper {
public Person map(PersonDto dto) {
if (dto == null) {
return null;
}
String name;
String surname;
name = dto.getName();
surname = dto.getSurname();
Person person = new Person( name, surname );
return person;
}
}
当不使用DI框架时,mappr实例可以通过org.mapstruct.factory.Mappers
类获取。仅需调用getMapper()
方法,传递要返回的mapper的接口类型。
Example 23. Using the Mappers factory
CarMapper mapper = Mappers.getMapper( CarMapper.class );
按照惯例,mapper接口应该定义一个名为INSTANCE
的成员,它持有mapper类型的一个实例。
Example 24. Declaring an instance of a mapper (interface)
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
CarDto carToCarDto(Car car);
}
Example 25. Declaring an instance of a mapper (abstract class)
@Mapper
public abstract class CarMapper {
public static final CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
CarDto carToCarDto(Car car);
}
这种模式使得使用者可以很有容易的使用mapper对象,而无需反复创建新的实例对象。
Example 26. Accessing a mapper
Car car = ...;
CarDto dto = CarMapper.INSTANCE.carToCarDto( car );
值得注意的是,MapStruct生成的映射器是无状态和线程安全的,因此可以安全地从多个线程同时访问。
如果你使用诸如CDI (Contexts and Dependency Injection for JavaTM EE)或者 Spring Framework的依赖注入框架,那么更加建议通过依赖注入获取mapper对象而不是通过上文描述的Mappers
类。您可以通过@Mapper#componentModel
或使用配置选项中描述的处理器选项来指定生成的映射器类应基于的组件模型。
目前支持CDI和Spring(后者通过其自定义注释或使用JSR 330注释)。请参阅配置选项,了解componentModel
属性的允许值,这些值与 mapstruct.defaultComponentModel
处理器选项相同。在这两种情况下,所需的注解将被添加到生成的映射器实现类中,以便使其同样受到依赖注入的影响。下面展示了一个使用CDI的例子。
Example 27. A mapper using the CDI component model
@Mapper(componentModel = "cdi")
public interface CarMapper {
CarDto carToCarDto(Car car);
}
生成的映射器实现将被标记为@ApplicationScoped
注解,因此可以使用@Inject
注解注入字段、构造参数等。
Example 28. Obtaining a mapper via dependency injection
@Inject
private CarMapper mapper;
使用其他映射器类的映射器(参见调用其他映射器)将使用配置的组件模型获得这些映射器。因此,如果前面例子中的CarMapper
使用了另一个映射器,那么这个其他映射器也必须是一个可注入的CDI Bean。
当使用依赖注入,时,你可以选择字段和构造函数注入。这可以通过@Mapper
或@MapperConfig
注解提供注入策略来实现。
Example 29. Using constructor injection
@Mapper(componentModel = "cdi", uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CarMapper {
CarDto carToCarDto(Car car);
}
生成的映射器将注入所有在uses属性中定义的类。当使用InjectionStrategy#CONSTRUCTOR
时,构造函数会有相应的注释,而字段不会。当使用InjectionStrategy#FIELD
时,注释在字段本身。目前,默认的注入策略是字段注入,但可以通过配置选项进行配置。建议使用构造函数注入,以简化测试。
Tips For abstract classes or decorators setter injection should be used.
映射的属性在源对象和目标对象中并非总是具有相同的类型。例如,一个属性在源Bean中可能是 int
类型,但在目标Bean中可能是 Long
类型。
另一个例子是对其他对象的引用,这些对象应被映射到目标模型中的相应类型例如,Car
类可能有一个 Person
类型的属性 driver
,在映射 Car
对象时,需要将其转换为 PersonDto
对象。
在本节,你将学习到如何MapStruct是如何应对数据类型转换的。
MapStruct在许多情况下会自动处理类型转换。例如,如果一个属性在源Bean中的类型是int
,而在目标Bean中的类型是String
,那么生成的代码将分别通过调用 String#valueOf(int)
和Integer#parseInt(String)
来执行隐式转换。
目前,以下转换是自动应用的:
int
和Integer
、boolean
和Boolean
等之间。生成的代码具有 null
感知运算,即当将封装类型转换为相应的基元类型时,将执行null
检查。int
和long
或byte
和Integer
之间。Converting from larger data types to smaller ones (e.g. from
long
toint
) can cause a value or precision loss. TheMapper
andMapperConfig
annotations have a methodtypeConversionPolicy
to control warnings / errors. Due to backward compatibility reasons the default value isReportingPolicy.IGNORE
.
String
之间,例如int
和String
或Boolean
和String
之间。可以指定由java.text.DecimalFormat
解析的格式字符串。Example 30. Conversion from int to String
@Mapper
public interface CarMapper {
@Mapping(source = "price", numberFormat = "$#.00")
CarDto carToCarDto(Car car);
@IterableMapping(numberFormat = "$#.00")
List<String> prices(List<Integer> prices);
}
enum
类型和String
java.math.BigInteger
,java.math.BigDecimal
)和Java元类型(包括它们的封装类)以及String之间。可以指定java.text.DecimalFormat
所理解的格式字符串。Example 31. Conversion from BigDecimal to String
@Mapper
public interface CarMapper {
@Mapping(source = "power", numberFormat = "#.##E0")
CarDto carToCarDto(Car car);
}
JAXBElement
和T
之间,List
和List
之间的转换。java.util.Calendar
/java.util.Date
和JAXB的XMLGregorianCalendar
之间的转换。java.util.Date
/XMLGregorianCalendar
和String
之间的转换。可以通过dateFormat
选项指定java.text.SimpleDateFormat
所理解的格式字符串,如:Example 32. Conversion from Date to String
@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);
}
org.joda.time.DateTime
、org.joda.time.LocalDateTime
、org.joda.time.LocalDate
、org.joda.time.LocalTime
和String
之间。java.text.SimpleDateFormat
理解的格式字符串可以通过dateFormat
选项指定(见上文)。org.joda.time.DateTime
和javax.xml.datatype.XMLGregorianCalendar
、java.util.Calendar
之间。org.joda.time.LocalDateTime
、org.joda.time.LocalDate
和javax.xml.datatype.XMLGregorianCalendar
、java.util.Date
之间。java.time.LocalDate
、java.time.LocalDateTime
和javax.xml.datatype.XMLGregorianCalendar
之间。java.time.ZonedDateTime
、java.time.LocalDateTime
、java.time.LocalDate
、java.time.LocalTime
、java.time.LocalTime
从Java 8 Date-Time包和String
之间。可以通过dateFormat
选项指定java.text.SimpleDateFormat
所理解的格式字符串(见上文)。java.time.Instant
、java.time.Duration
、java.time.Period
之间使用Java 8 Date-Time包中的parse
方法从String
映射到String
,并使用toString
映射到String
。java.time.ZonedDateTime
和java.util.Date
之间,当从给定的Date
映射一个ZonedDateTime
时,使用系统默认的时区。java.time.LocalDateTime
和java.util.Date
之间,使用UTC作为时区。java.time.LocalDate
和java.util.Date
/java.sql.Date
之间,使用UTC作为时区。java.time.Instant
和java.util.Date
之间。java.time.ZonedDateTime
和java.util.Calendar
之间。java.sql.Date
和java.util.Date
之间。java.sql.Time
和java.util.Date
之间。java.sql.Timestamp
和java.util.Date
之间。Mapping#dateFormat
,会导致使用默认的模式和日期格式符号来转换默认的本地环境。这个规则的一个例外是XmlGregorianCalendar
,它的结果是根据XML Schema 1.0 Part 2, Section 3.2.7-14.1, Lexical Representation解析String
。java.util.Currency
和String
之间。
String
转换时,值需要是有效的ISO-4217字母代码,否则会抛出IllegalArgumentException
。通常情况下,一个对象不仅有基元属性,还可以引用其他对象。例如,Car
类可以包含对 Person
对象(代表汽车司机)的引用,该对象应映射到 CarDto
类引用的 PersonDto
对象。
在这种情况下,只要为被引用的对象类型也定义一个映射方法即可。
Example 33. Mapper with one mapping method using another
@Mapper
public interface CarMapper {
CarDto carToCarDto(Car car);
PersonDto personToPersonDto(Person person);
}
carToCarDto()
方法生成的代码将调用personToPersonDto()
方法对driver
属性进行映射,而personToPersonDto()
生成的实现则执行person
对象的映射。
这样就可以映射任意的深层对象图。当从实体映射到数据传输对象时,通常需要在某一点上切断对其他实体的引用。要做到这一点,可以实现一个自定义的映射方法(见下一节),例如,将被引用的实体映射到目标对象中的id。
当生成映射方法的实现时,MapStruct将对源对象和目标对象中的每个属性对应用以下规则。