mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
This sentence is talking about `@Target`, not `@Retention`. Also, let's use 'type' instead of 'class' to line up with `ElementType.TYPE`.
2915 lines
113 KiB
Plaintext
2915 lines
113 KiB
Plaintext
= MapStruct {mapstructVersion} Reference Guide
|
||
:revdate: {docdate}
|
||
:toc: right
|
||
:sectanchors:
|
||
:Author: Gunnar Morling, Andreas Gudian, Sjaak Derksen, Filip Hrisafov and the MapStruct community
|
||
:processor-dir: ../../../../processor
|
||
:processor-ap-test: {processor-dir}/src/test/java/org/mapstruct/ap/test
|
||
:processor-ap-main: {processor-dir}/src/main/java/org/mapstruct/ap
|
||
:integration-tests-dir: ../../../../integrationtest
|
||
|
||
[[Preface]]
|
||
== Preface
|
||
This is the reference documentation of MapStruct, an annotation processor for generating type-safe, performant and dependency-free bean mapping code.
|
||
This guide covers all the functionality provided by MapStruct. In case this guide doesn't answer all your questions just join the MapStruct https://groups.google.com/forum/?fromgroups#!forum/mapstruct-users[Google group] to get help.
|
||
|
||
You found a typo or other error in this guide? Please let us know by opening an issue in the https://github.com/mapstruct/mapstruct[MapStruct GitHub repository],
|
||
or, better yet, help the community and send a pull request for fixing it!
|
||
|
||
This work is licensed under the http://creativecommons.org/licenses/by-sa/4.0/[Creative Commons Attribution-ShareAlike 4.0 International License].
|
||
|
||
:numbered:
|
||
|
||
[[introduction]]
|
||
== Introduction
|
||
|
||
MapStruct is a Java http://docs.oracle.com/javase/6/docs/technotes/guides/apt/index.html[annotation processor] for the generation of type-safe bean mapping classes.
|
||
|
||
All you have to do is to define a mapper interface which declares any required mapping methods. During compilation, MapStruct will generate an implementation of this interface. This implementation uses plain Java method invocations for mapping between source and target objects, i.e. no reflection or similar.
|
||
|
||
Compared to writing mapping code from hand, MapStruct saves time by generating code which is tedious and error-prone to write. Following a convention over configuration approach, MapStruct uses sensible defaults but steps out of your way when it comes to configuring or implementing special behavior.
|
||
|
||
Compared to dynamic mapping frameworks, MapStruct offers the following advantages:
|
||
|
||
* Fast execution by using plain method invocations instead of reflection
|
||
* Compile-time type safety: Only objects and attributes mapping to each other can be mapped, no accidental mapping of an order entity into a customer DTO etc.
|
||
* Clear error-reports at build time, if
|
||
** mappings are incomplete (not all target properties are mapped)
|
||
** mappings are incorrect (cannot find a proper mapping method or type conversion)
|
||
|
||
[[setup]]
|
||
== Set up
|
||
|
||
MapStruct is a Java annotation processor based on http://www.jcp.org/en/jsr/detail?id=269[JSR 269] and as such can be used within command line builds (javac, Ant, Maven etc.) as well as from within your IDE.
|
||
|
||
It comprises the following artifacts:
|
||
|
||
* _org.mapstruct:mapstruct_: contains the required annotations such as `@Mapping`
|
||
* _org.mapstruct:mapstruct-processor_: contains the annotation processor which generates mapper implementations
|
||
|
||
=== Apache Maven
|
||
|
||
For Maven based projects add the following to your POM file in order to use MapStruct:
|
||
|
||
.Maven configuration
|
||
====
|
||
[source, xml, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
...
|
||
<properties>
|
||
<org.mapstruct.version>{mapstructVersion}</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.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>
|
||
</configuration>
|
||
</plugin>
|
||
</plugins>
|
||
</build>
|
||
...
|
||
----
|
||
====
|
||
|
||
[TIP]
|
||
====
|
||
If you are working with the Eclipse IDE, make sure to have a current version of the http://www.eclipse.org/m2e/[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: `<m2e.apt.activation>jdt_apt</m2e.apt.activation>`.
|
||
|
||
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
|
||
|
||
Add the following to your Gradle build file in order to enable MapStruct:
|
||
|
||
.Gradle configuration (3.4 and later)
|
||
====
|
||
[source, groovy, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
...
|
||
plugins {
|
||
...
|
||
id 'net.ltgt.apt' version '0.20'
|
||
}
|
||
|
||
// You can integrate with your IDEs.
|
||
// See more details: https://github.com/tbroyer/gradle-apt-plugin#usage-with-ides
|
||
apply plugin: 'net.ltgt.apt-idea'
|
||
apply plugin: 'net.ltgt.apt-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}"
|
||
}
|
||
...
|
||
----
|
||
====
|
||
.Gradle (3.3 and older)
|
||
====
|
||
[source, groovy, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
...
|
||
plugins {
|
||
...
|
||
id 'net.ltgt.apt' version '0.20'
|
||
}
|
||
|
||
// You can integrate with your IDEs.
|
||
// See more details: https://github.com/tbroyer/gradle-apt-plugin#usage-with-ides
|
||
apply plugin: 'net.ltgt.apt-idea'
|
||
apply plugin: 'net.ltgt.apt-eclipse'
|
||
|
||
dependencies {
|
||
...
|
||
compile "org.mapstruct:mapstruct:${mapstructVersion}"
|
||
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
|
||
|
||
// If you are using mapstruct in test code
|
||
testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
|
||
}
|
||
...
|
||
----
|
||
====
|
||
|
||
You can find a complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-on-gradle[mapstruct-examples] project on GitHub.
|
||
|
||
|
||
=== Apache Ant
|
||
|
||
Add the `javac` task configured as follows to your _build.xml_ file in order to enable MapStruct in your Ant-based project. Adjust the paths as required for your project layout.
|
||
|
||
.Ant configuration
|
||
====
|
||
[source, xml, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
...
|
||
<javac
|
||
srcdir="src/main/java"
|
||
destdir="target/classes"
|
||
classpath="path/to/mapstruct-{mapstructVersion}.jar">
|
||
<compilerarg line="-processorpath path/to/mapstruct-processor-{mapstructVersion}.jar"/>
|
||
<compilerarg line="-s target/generated-sources"/>
|
||
</javac>
|
||
...
|
||
----
|
||
====
|
||
|
||
You can find a complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-on-ant[mapstruct-examples] project on GitHub.
|
||
|
||
[[configuration-options]]
|
||
=== Configuration options
|
||
|
||
The MapStruct code generator can be configured using _annotation processor options_.
|
||
|
||
When invoking javac directly, these options are passed to the compiler in the form _-Akey=value_. When using MapStruct via Maven, any processor options can be passed using an `options` element within the configuration of the Maven processor plug-in like this:
|
||
|
||
.Maven configuration
|
||
====
|
||
[source, xml, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
...
|
||
<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>
|
||
...
|
||
----
|
||
====
|
||
|
||
.Gradle configuration
|
||
====
|
||
[source, groovy, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
...
|
||
compileJava {
|
||
options.compilerArgs = [
|
||
'-Amapstruct.suppressGeneratorTimestamp=true',
|
||
'-Amapstruct.suppressGeneratorVersionInfoComment=true'
|
||
]
|
||
}
|
||
...
|
||
----
|
||
====
|
||
|
||
The following options exist:
|
||
|
||
.MapStruct processor options
|
||
[cols="1,2a,1"]
|
||
|===
|
||
|Option|Purpose|Default
|
||
|
||
|`mapstruct.
|
||
suppressGeneratorTimestamp`
|
||
|If set to `true`, the creation of a time stamp in the `@Generated` annotation in the generated mapper classes is suppressed.
|
||
|`false`
|
||
|
||
|`mapstruct.
|
||
suppressGeneratorVersionInfoComment`
|
||
|If set to `true`, the creation of the `comment` attribute in the `@Generated` annotation in the generated mapper classes is suppressed. The comment contains information about the version of MapStruct and about the compiler used for the annotation processing.
|
||
|`false`
|
||
|
||
|`mapstruct.defaultComponentModel`
|
||
|The name of the component model (see <<retrieving-mapper>>) based on which mappers should be generated.
|
||
|
||
Supported values are:
|
||
|
||
* `default`: the mapper uses no component model, instances are typically retrieved via `Mappers#getMapper(Class)`
|
||
* `cdi`: the generated mapper is an application-scoped CDI bean and can be retrieved via `@Inject`
|
||
* `spring`: the generated mapper is a singleton-scoped Spring bean and can be retrieved via `@Autowired`
|
||
* `jsr330`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject`, e.g. using Spring
|
||
|
||
If a component model is given for a specific mapper via `@Mapper#componentModel()`, the value from the annotation takes precedence.
|
||
|`default`
|
||
|
||
|`mapstruct.unmappedTargetPolicy`
|
||
|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:
|
||
|
||
* `ERROR`: any unmapped target property will cause the mapping code generation to fail
|
||
* `WARN`: any unmapped target property will cause a warning at build time
|
||
* `IGNORE`: unmapped target properties are ignored
|
||
|
||
If a policy is given for a specific mapper via `@Mapper#unmappedTargetPolicy()`, the value from the annotation takes precedence.
|
||
|`WARN`
|
||
|===
|
||
|
||
=== Using MapStruct on Java 9
|
||
|
||
MapStruct can be used with Java 9 (JPMS), support for it is experimental.
|
||
|
||
A core theme of Java 9 is the modularization of the JDK. One effect of this is that a specific module needs to be enabled for a project in order to use the `javax.annotation.Generated` annotation. `@Generated` is added by MapStruct to generated mapper classes to tag them as generated code, stating the date of generation, the generator version etc.
|
||
|
||
To allow usage of the `@Generated` annotation the module _java.xml.ws.annotation_ must be enabled. When using Maven, this can be done like this:
|
||
|
||
export MAVEN_OPTS="--add-modules java.xml.ws.annotation"
|
||
|
||
If the `@Generated` annotation is not available, MapStruct will detect this situation and not add it to generated mappers.
|
||
|
||
[NOTE]
|
||
=====
|
||
In Java 9 `java.annotation.processing.Generated` was added (part of the `java.compiler` module),
|
||
if this annotation is available then it will be used.
|
||
=====
|
||
|
||
[[defining-mapper]]
|
||
== Defining a mapper
|
||
|
||
In this section you'll learn how to define a bean mapper with MapStruct and which options you have to do so.
|
||
|
||
[[basic-mappings]]
|
||
=== Basic mappings
|
||
|
||
To create a mapper simply define a Java interface with the required mapping method(s) and annotate it with the `org.mapstruct.Mapper` annotation:
|
||
|
||
.Java interface to define a mapper
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@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);
|
||
}
|
||
----
|
||
====
|
||
|
||
The `@Mapper` annotation causes the MapStruct code generator to create an implementation of the `CarMapper` interface during build-time.
|
||
|
||
In the generated method implementations all readable properties from the source type (e.g. `Car`) will be copied into the corresponding property in the target type (e.g. `CarDto`):
|
||
|
||
* When a property has the same name as its target entity counterpart, it will be mapped implicitly.
|
||
* When a property has a different name in the target entity, its name can be specified via the `@Mapping` annotation.
|
||
|
||
[TIP]
|
||
====
|
||
The property name as defined in the http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans specification] must be specified in the `@Mapping` annotation, e.g. _seatCount_ for a property with the accessor methods `getSeatCount()` and `setSeatCount()`.
|
||
====
|
||
[TIP]
|
||
====
|
||
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.
|
||
====
|
||
[TIP]
|
||
====
|
||
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;
|
||
}
|
||
```
|
||
====
|
||
|
||
|
||
To get a better understanding of what MapStruct does have a look at the following implementation of the `carToCarDto()` method as generated by MapStruct:
|
||
|
||
.Code generated by MapStruct
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
// 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;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
The general philosophy of MapStruct is to generate code which looks as much as possible as if you had written it yourself from hand. In particular this means that the values are copied from source to target by plain getter/setter invocations instead of reflection or similar.
|
||
|
||
As the example shows the generated code takes into account any name mappings specified via `@Mapping`.
|
||
If the type of a mapped attribute is different in source and target entity,
|
||
MapStruct will either apply an automatic conversion (as e.g. for the _price_ property, see also <<implicit-type-conversions>>)
|
||
or optionally invoke / create another mapping method (as e.g. for the _driver_ / _engine_ property, see also <<mapping-object-references>>).
|
||
MapStruct will only create a new mapping method if and only if the source and target property are properties of a Bean and they themselves are Beans or simple properties.
|
||
i.e. they are not `Collection` or `Map` type properties.
|
||
|
||
Collection-typed attributes with the same element type will be copied by creating a new instance of the target collection type containing the elements from the source property. For collection-typed attributes with different element types each element will be mapped individually and added to the target collection (see <<mapping-collections>>).
|
||
|
||
MapStruct takes all public properties of the source and target types into account. This includes properties declared on super-types.
|
||
|
||
[[adding-custom-methods]]
|
||
=== Adding custom methods to mappers
|
||
|
||
In some cases it can be required to manually implement a specific mapping from one type to another which can't be generated by MapStruct. One way to handle this is to implement the custom method on another class which then is used by mappers generated by MapStruct (see <<invoking-other-mappers>>).
|
||
|
||
Alternatively, when using Java 8 or later, you can implement custom methods directly in a mapper interface as default methods. The generated code will invoke the default methods if the argument and return types match.
|
||
|
||
As an example let's assume the mapping from `Person` to `PersonDto` requires some special logic which can't be generated by MapStruct. You could then define the mapper from the previous example like this:
|
||
|
||
.Mapper which defines a custom mapping with a default method
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface CarMapper {
|
||
|
||
@Mapping(...)
|
||
...
|
||
CarDto carToCarDto(Car car);
|
||
|
||
default PersonDto personToPersonDto(Person person) {
|
||
//hand-written mapping logic
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
The class generated by MapStruct implements the method `carToCarDto()`. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute.
|
||
|
||
A mapper could also be defined in the form of an abstract class instead of an interface and implement the custom methods directly in the mapper class. In this case MapStruct will generate an extension of the abstract class with implementations of all abstract methods. An advantage of this approach over declaring default methods is that additional fields could be declared in the mapper class.
|
||
|
||
The previous example where the mapping from `Person` to `PersonDto` requires some special logic could then be defined like this:
|
||
|
||
.Mapper defined by an abstract class
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public abstract class CarMapper {
|
||
|
||
@Mapping(...)
|
||
...
|
||
public abstract CarDto carToCarDto(Car car);
|
||
|
||
public PersonDto personToPersonDto(Person person) {
|
||
//hand-written mapping logic
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
MapStruct will generate a sub-class of `CarMapper` with an implementation of the `carToCarDto()` method as it is declared abstract. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute.
|
||
|
||
[[mappings-with-several-source-parameters]]
|
||
=== Mapping methods with several source parameters
|
||
|
||
MapStruct also supports mapping methods with several source parameters. This is useful e.g. in order to combine several entities into one data transfer object. The following shows an example:
|
||
|
||
.Mapping method with several source parameters
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface AddressMapper {
|
||
|
||
@Mapping(source = "person.description", target = "description")
|
||
@Mapping(source = "address.houseNo", target = "houseNumber")
|
||
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
|
||
}
|
||
----
|
||
====
|
||
|
||
The shown mapping method takes two source parameters and returns a combined target object. As with single-parameter mapping methods properties are mapped by name.
|
||
|
||
In case several source objects define a property with the same name, the source parameter from which to retrieve the property must be specified using the `@Mapping` annotation as shown for the `description` property in the example. An error will be raised when such an ambiguity is not resolved. For properties which only exist once in the given source objects it is optional to specify the source parameter's name as it can be determined automatically.
|
||
|
||
[WARNING]
|
||
====
|
||
Specifying the parameter in which the property resides is mandatory when using the `@Mapping` annotation.
|
||
====
|
||
|
||
[TIP]
|
||
====
|
||
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 also offers the possibility to directly refer to a source parameter.
|
||
|
||
.Mapping method directly referring to a source parameter
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface AddressMapper {
|
||
|
||
@Mapping(source = "person.description", target = "description")
|
||
@Mapping(source = "hn", target = "houseNumber")
|
||
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
|
||
}
|
||
----
|
||
====
|
||
|
||
In this case the source parameter is directly mapped into the target as the example above demonstrates. The parameter `hn`, a non bean type (in this case `java.lang.Integer`) is mapped to `houseNumber`.
|
||
|
||
[[updating-bean-instances]]
|
||
=== Updating existing bean instances
|
||
|
||
In some cases you need mappings which don't create a new instance of the target type but instead update an existing instance of that type. This sort of mapping can be realized by adding a parameter for the target object and marking this parameter with `@MappingTarget`. The following shows an example:
|
||
|
||
.Update method
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface CarMapper {
|
||
|
||
void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
|
||
}
|
||
----
|
||
====
|
||
|
||
The generated code of the `updateCarFromDto()` method will update the passed `Car` instance with the properties from the given `CarDto` object. There may be only one parameter marked as mapping target. Instead of `void` you may also set the method's return type to the type of the target parameter, which will cause the generated implementation to update the passed mapping target and return it as well. This allows for fluent invocations of mapping methods.
|
||
|
||
For `CollectionMappingStrategy.ACCESSOR_ONLY` Collection- or map-typed properties of the target bean to be updated will be cleared and then populated with the values from the corresponding source collection or map. Otherwise, For `CollectionMappingStrategy.ADDER_PREFERRED` or `CollectionMappingStrategy.TARGET_IMMUTABLE` the target will not be cleared and the values will be populated immediately.
|
||
|
||
[[direct-field-mappings]]
|
||
=== Mappings with direct field access
|
||
|
||
MapStruct also supports mappings of `public` fields that have no getters/setters. MapStruct will
|
||
use the fields as read/write accessor if it cannot find suitable getter/setter methods for the property.
|
||
|
||
A field is considered as a read accessor if it is `public` or `public final`. If a field is `static` it is not
|
||
considered as a read accessor.
|
||
|
||
A field is considered as a write accessor only if it is `public`. If a field is `final` and/or `static` it is not
|
||
considered as a write accessor.
|
||
|
||
Small example:
|
||
|
||
.Example classes for mapping
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
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);
|
||
}
|
||
----
|
||
====
|
||
|
||
For the configuration from above, the generated mapper looks like:
|
||
|
||
.Generated mapper for example classes
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
// 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();
|
||
// ...
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
You can find the complete example in the
|
||
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-field-mapping[mapstruct-examples-field-mapping]
|
||
project on GitHub.
|
||
|
||
[[mapping-with-builders]]
|
||
=== Using builders
|
||
|
||
MapStruct also supports mapping of immutable types via builders.
|
||
When performing a mapping MapStruct checks if there is a builder for the type being mapped.
|
||
This is done via the `BuilderProvider` SPI.
|
||
If a Builder exists for a certain type, then that builder will be used for the mappings.
|
||
|
||
The default implementation of the `BuilderProvider` assumes the following:
|
||
|
||
* The type has a parameterless public static builder creation method that returns a builder.
|
||
So for example `Person` has a public static method that returns `PersonBuilder`.
|
||
* The builder type has a parameterless public method (build method) that returns the type being build
|
||
In our example `PersonBuilder` has a method returning `Person`.
|
||
* In case there are multiple build methods, MapStruct will look for a method called `build`, if such method exists
|
||
then this would be used, otherwise a compilation error would be created.
|
||
* A specific build method can be defined by using `@Builder` within: `@BeanMapping`, `@Mapper` or `@MapperConfig`
|
||
* In case there are multiple builder creation methods that satisfy the above conditions then a `MoreThanOneBuilderCreationMethodException`
|
||
will be thrown from the `DefaultBuilderProvider` SPI.
|
||
In case of a `MoreThanOneBuilderCreationMethodException` MapStruct will write a warning in the compilation and not use any builder.
|
||
|
||
If such type is found then MapStruct will use that type to perform the mapping to (i.e. it will look for setters into that type).
|
||
To finish the mapping MapStruct generates code that will invoke the build method of the builder.
|
||
|
||
[NOTE]
|
||
======
|
||
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.
|
||
======
|
||
|
||
.Person with Builder example
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
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 );
|
||
}
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
.Person Mapper definition
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public interface PersonMapper {
|
||
|
||
Person map(PersonDto dto);
|
||
}
|
||
----
|
||
====
|
||
|
||
.Generated mapper with builder
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
// 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();
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
Supported builder frameworks:
|
||
|
||
* https://projectlombok.org/[Lombok] - requires having the Lombok classes in a separate module. See for more information https://github.com/rzwitserloot/lombok/issues/1538[rzwitserloot/lombok#1538]
|
||
* https://github.com/google/auto/blob/master/value/userguide/index.md[AutoValue]
|
||
* https://immutables.github.io/[Immutables] - When Immutables are present on the annotation processor path then the `ImmutablesAccessorNamingStrategy` and `ImmutablesBuilderProvider` would be used by default
|
||
* https://github.com/google/FreeBuilder[FreeBuilder] - When FreeBuilder is present on the annotation processor path then the `FreeBuilderAccessorNamingStrategy` would be used by default.
|
||
When using FreeBuilder then the JavaBean convention should be followed, otherwise MapStruct won't recognize the fluent getters.
|
||
* It also works for custom builders (handwritten ones) if the implementation supports the defined rules for the default `BuilderProvider`.
|
||
Otherwise, you would need to write a custom `BuilderProvider`
|
||
|
||
[TIP]
|
||
====
|
||
In case you want to disable using builders then you can use the `NoOpBuilderProvider` by creating a `org.mapstruct.ap.spi.BuilderProvider` file in the `META-INF/services` directory with `org.mapstruct.ap.spi.NoOpBuilderProvider` as it's content.
|
||
====
|
||
|
||
[[retrieving-mapper]]
|
||
== Retrieving a mapper
|
||
|
||
[[mappers-factory]]
|
||
=== The Mappers factory (no dependency injection)
|
||
|
||
When not using a DI framework, Mapper instances can be retrieved via the `org.mapstruct.factory.Mappers` class. Just invoke the `getMapper()` method, passing the interface type of the mapper to return:
|
||
|
||
.Using the Mappers factory
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
CarMapper mapper = Mappers.getMapper( CarMapper.class );
|
||
----
|
||
====
|
||
|
||
By convention, a mapper interface should define a member called `INSTANCE` which holds a single instance of the mapper type:
|
||
|
||
.Declaring an instance of a mapper (interface)
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface CarMapper {
|
||
|
||
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
|
||
|
||
CarDto carToCarDto(Car car);
|
||
}
|
||
|
||
----
|
||
====
|
||
|
||
.Declaring an instance of a mapper (abstract class)
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public abstract class CarMapper {
|
||
|
||
public static final CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
|
||
|
||
CarDto carToCarDto(Car car);
|
||
}
|
||
|
||
----
|
||
====
|
||
|
||
This pattern makes it very easy for clients to use mapper objects without repeatedly instantiating new instances:
|
||
|
||
.Accessing a mapper
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
Car car = ...;
|
||
CarDto dto = CarMapper.INSTANCE.carToCarDto( car );
|
||
----
|
||
====
|
||
|
||
|
||
Note that mappers generated by MapStruct are stateless and thread-safe and thus can safely be accessed from several threads at the same time.
|
||
|
||
[[using-dependency-injection]]
|
||
=== Using dependency injection
|
||
|
||
If you're working with a dependency injection framework such as http://jcp.org/en/jsr/detail?id=346[CDI] (Contexts and Dependency Injection for Java^TM^ EE) or the http://www.springsource.org/spring-framework[Spring Framework], it is recommended to obtain mapper objects via dependency injection and *not* via the `Mappers` class as described above. For that purpose you can specify the component model which generated mapper classes should be based on either via `@Mapper#componentModel` or using a processor option as described in <<configuration-options>>.
|
||
|
||
Currently there is support for CDI and Spring (the latter either via its custom annotations or using the JSR 330 annotations). See <<configuration-options>> for the allowed values of the `componentModel` attribute which are the same as for the `mapstruct.defaultComponentModel` processor option. In both cases the required annotations will be added to the generated mapper implementations classes in order to make the same subject to dependency injection. The following shows an example using CDI:
|
||
|
||
.A mapper using the CDI component model
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper(componentModel = "cdi")
|
||
public interface CarMapper {
|
||
|
||
CarDto carToCarDto(Car car);
|
||
}
|
||
|
||
----
|
||
====
|
||
|
||
The generated mapper implementation will be marked with the `@ApplicationScoped` annotation and thus can be injected into fields, constructor arguments etc. using the `@Inject` annotation:
|
||
|
||
.Obtaining a mapper via dependency injection
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Inject
|
||
private CarMapper mapper;
|
||
----
|
||
====
|
||
|
||
A mapper which uses other mapper classes (see <<invoking-other-mappers>>) will obtain these mappers using the configured component model. So if `CarMapper` from the previous example was using another mapper, this other mapper would have to be an injectable CDI bean as well.
|
||
|
||
[[injection-strategy]]
|
||
=== Injection strategy
|
||
|
||
When using <<using-dependency-injection,dependency injection>>, you can choose between field and constructor injection.
|
||
This can be done by either providing the injection strategy via `@Mapper` or `@MapperConfig` annotation.
|
||
|
||
.Using constructor injection
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper(componentModel = "cdi", uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
|
||
public interface CarMapper {
|
||
CarDto carToCarDto(Car car);
|
||
}
|
||
----
|
||
====
|
||
|
||
The generated mapper will inject all classes defined in the **uses** attribute.
|
||
When `InjectionStrategy#CONSTRUCTOR` is used, the constructor will have the appropriate annotation and the fields won't.
|
||
When `InjectionStrategy#FIELD` is used, the annotation is on the field itself.
|
||
For now, the default injection strategy is field injection.
|
||
It is recommended to use constructor injection to simplify testing.
|
||
|
||
[TIP]
|
||
====
|
||
For abstract classes or decorators setter injection should be used.
|
||
====
|
||
|
||
[[datatype-conversions]]
|
||
== Data type conversions
|
||
|
||
Not always a mapped attribute has the same type in the source and target objects. For instance an attribute may be of type `int` in the source bean but of type `Long` in the target bean.
|
||
|
||
Another example are references to other objects which should be mapped to the corresponding types in the target model. E.g. the class `Car` might have a property `driver` of the type `Person` which needs to be converted into a `PersonDto` object when mapping a `Car` object.
|
||
|
||
In this section you'll learn how MapStruct deals with such data type conversions.
|
||
|
||
[[implicit-type-conversions]]
|
||
=== Implicit type conversions
|
||
|
||
MapStruct takes care of type conversions automatically in many cases. If for instance an attribute is of type `int` in the source bean but of type `String` in the target bean, the generated code will transparently perform a conversion by calling `String#valueOf(int)` and `Integer#parseInt(String)`, respectively.
|
||
|
||
Currently the following conversions are applied automatically:
|
||
|
||
* Between all Java primitive data types and their corresponding wrapper types, e.g. between `int` and `Integer`, `boolean` and `Boolean` etc. The generated code is `null` aware, i.e. when converting a wrapper type into the corresponding primitive type a `null` check will be performed.
|
||
|
||
* Between all Java primitive number types and the wrapper types, e.g. between `int` and `long` or `byte` and `Integer`.
|
||
|
||
[WARNING]
|
||
====
|
||
Converting from larger data types to smaller ones (e.g. from `long` to `int`) can cause a value or precision loss. The `Mapper` and `MapperConfig` annotations have a method `typeConversionPolicy` to control warnings / errors. Due to backward compatibility reasons the default value is 'ReportingPolicy.IGNORE`.
|
||
====
|
||
|
||
* Between all Java primitive types (including their wrappers) and `String`, e.g. between `int` and `String` or `Boolean` and `String`. A format string as understood by `java.text.DecimalFormat` can be specified.
|
||
|
||
.Conversion from int to String
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface CarMapper {
|
||
|
||
@Mapping(source = "price", numberFormat = "$#.00")
|
||
CarDto carToCarDto(Car car);
|
||
|
||
@IterableMapping(numberFormat = "$#.00")
|
||
List<String> prices(List<Integer> prices);
|
||
}
|
||
----
|
||
====
|
||
* Between `enum` types and `String`.
|
||
|
||
* Between big number types (`java.math.BigInteger`, `java.math.BigDecimal`) and Java primitive types (including their wrappers) as well as String. A format string as understood by `java.text.DecimalFormat` can be specified.
|
||
|
||
.Conversion from BigDecimal to String
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface CarMapper {
|
||
|
||
@Mapping(source = "power", numberFormat = "#.##E0")
|
||
CarDto carToCarDto(Car car);
|
||
|
||
}
|
||
----
|
||
====
|
||
|
||
|
||
* Between `JAXBElement<T>` and `T`, `List<JAXBElement<T>>` and `List<T>`
|
||
|
||
* Between `java.util.Calendar`/`java.util.Date` and JAXB's `XMLGregorianCalendar`
|
||
|
||
* Between `java.util.Date`/`XMLGregorianCalendar` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option as this:
|
||
|
||
.Conversion from Date to String
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@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);
|
||
}
|
||
----
|
||
====
|
||
|
||
* Between Jodas `org.joda.time.DateTime`, `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate`, `org.joda.time.LocalTime` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above).
|
||
|
||
* Between Jodas `org.joda.time.DateTime` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Calendar`.
|
||
|
||
* Between Jodas `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Date`.
|
||
|
||
* Between `java.time.ZonedDateTime`, `java.time.LocalDateTime`, `java.time.LocalDate`, `java.time.LocalTime` from Java 8 Date-Time package and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above).
|
||
|
||
* Between `java.time.Instant`, `java.time.Duration`, `java.time.Period` from Java 8 Date-Time package and `String` using the `parse` method in each class to map from `String` and using `toString` to map into `String`.
|
||
|
||
* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Date` where, when mapping a `ZonedDateTime` from a given `Date`, the system default timezone is used.
|
||
|
||
* Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.util.Date` where timezone UTC is used as the timezone.
|
||
|
||
* Between `java.time.LocalDate` from Java 8 Date-Time package and `java.util.Date` / `java.sql.Date` where timezone UTC is used as the timezone.
|
||
|
||
* Between `java.time.Instant` from Java 8 Date-Time package and `java.util.Date`.
|
||
|
||
* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Calendar`.
|
||
|
||
* Between `java.sql.Date` and `java.util.Date`
|
||
|
||
* Between `java.sql.Time` and `java.util.Date`
|
||
|
||
* Between `java.sql.Timestamp` and `java.util.Date`
|
||
|
||
* When converting from a `String`, omitting `Mapping#dateFormat`, it leads to usage of the default pattern and date format symbols for the default locale. An exception to this rule is `XmlGregorianCalendar` which results in parsing the `String` according to http://www.w3.org/TR/xmlschema-2/#dateTime[XML Schema 1.0 Part 2, Section 3.2.7-14.1, Lexical Representation].
|
||
|
||
* Between `java.util.Currency` and `String`.
|
||
** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/ISO_4217[ISO-4217] alphabetic code otherwise an `IllegalArgumentException` is thrown
|
||
|
||
[[mapping-object-references]]
|
||
=== Mapping object references
|
||
|
||
Typically an object has not only primitive attributes but also references other objects. E.g. the `Car` class could contain a reference to a `Person` object (representing the car's driver) which should be mapped to a `PersonDto` object referenced by the `CarDto` class.
|
||
|
||
In this case just define a mapping method for the referenced object type as well:
|
||
|
||
.Mapper with one mapping method using another
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface CarMapper {
|
||
|
||
CarDto carToCarDto(Car car);
|
||
|
||
PersonDto personToPersonDto(Person person);
|
||
}
|
||
----
|
||
====
|
||
|
||
The generated code for the `carToCarDto()` method will invoke the `personToPersonDto()` method for mapping the `driver` attribute, while the generated implementation for `personToPersonDto()` performs the mapping of person objects.
|
||
|
||
That way it is possible to map arbitrary deep object graphs. When mapping from entities into data transfer objects it is often useful to cut references to other entities at a certain point. To do so, implement a custom mapping method (see the next section) which e.g. maps a referenced entity to its id in the target object.
|
||
|
||
When generating the implementation of a mapping method, MapStruct will apply the following routine for each attribute pair in the source and target object:
|
||
|
||
* If source and target attribute have the same type, the value will be simply copied from source to target. If the attribute is a collection (e.g. a `List`) a copy of the collection will be set into the target attribute.
|
||
* If source and target attribute type differ, check whether there is another mapping method which has the type of the source attribute as parameter type and the type of the target attribute as return type. If such a method exists it will be invoked in the generated mapping implementation.
|
||
* If no such method exists MapStruct will look whether a built-in conversion for the source and target type of the attribute exists. If this is the case, the generated mapping code will apply this conversion.
|
||
* If no such method was found MapStruct will try to generate an automatic sub-mapping method that will do the mapping between the source and target attributes.
|
||
* If MapStruct could not create a name based mapping method an error will be raised at build time, indicating the non-mappable attribute and its path.
|
||
|
||
[NOTE]
|
||
====
|
||
In order to stop MapStruct from generating automatic sub-mapping methods, one can use `@Mapper( disableSubMappingMethodsGeneration = true )`.
|
||
====
|
||
|
||
[NOTE]
|
||
====
|
||
During the generation of automatic sub-mapping methods <<shared-configurations>> will not be taken into consideration, yet.
|
||
Follow issue https://github.com/mapstruct/mapstruct/issues/1086[#1086] for more information.
|
||
====
|
||
|
||
include::controlling-nested-bean-mappings.asciidoc[]
|
||
|
||
[[invoking-other-mappers]]
|
||
=== Invoking other mappers
|
||
|
||
In addition to methods defined on the same mapper type MapStruct can also invoke mapping methods defined in other classes, be it mappers generated by MapStruct or hand-written mapping methods. This can be useful to structure your mapping code in several classes (e.g. with one mapper type per application module) or if you want to provide custom mapping logic which can't be generated by MapStruct.
|
||
|
||
For instance the `Car` class might contain an attribute `manufacturingDate` while the corresponding DTO attribute is of type String. In order to map this attribute, you could implement a mapper class like this:
|
||
|
||
.Manually implemented mapper class
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
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 );
|
||
}
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
In the `@Mapper` annotation at the `CarMapper` interface reference the `DateMapper` class like this:
|
||
|
||
.Referencing another mapper class
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper(uses=DateMapper.class)
|
||
public class CarMapper {
|
||
|
||
CarDto carToCarDto(Car car);
|
||
}
|
||
----
|
||
====
|
||
|
||
When generating code for the implementation of the `carToCarDto()` method, MapStruct will look for a method which maps a `Date` object into a String, find it on the `DateMapper` class and generate an invocation of `asString()` for mapping the `manufacturingDate` attribute.
|
||
|
||
Generated mappers retrieve referenced mappers using the component model configured for them. If e.g. CDI was used as component model for `CarMapper`, `DateMapper` would have to be a CDI bean as well. When using the default component model, any hand-written mapper classes to be referenced by MapStruct generated mappers must declare a public no-args constructor in order to be instantiable.
|
||
|
||
[[passing-target-type]]
|
||
=== Passing the mapping target type to custom mappers
|
||
|
||
When having a custom mapper hooked into the generated mapper with `@Mapper#uses()`, an additional parameter of type `Class` (or a super-type of it) can be defined in the custom mapping method in order to perform general mapping tasks for specific target object types. That attribute must be annotated with `@TargetType` for MapStruct to generate calls that pass the `Class` instance representing the corresponding property type of the target bean.
|
||
|
||
For instance, the `CarDto` could have a property `owner` of type `Reference` that contains the primary key of a `Person` entity. You could now create a generic custom mapper that resolves any `Reference` objects to their corresponding managed JPA entity instances.
|
||
|
||
.Mapping method expecting mapping target type as parameter
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@ApplicationScoped // CDI component model
|
||
public class ReferenceMapper {
|
||
|
||
@PersistenceContext
|
||
private EntityManager entityManager;
|
||
|
||
public <T extends BaseEntity> T resolve(Reference reference, @TargetType Class<T> entityClass) {
|
||
return reference != null ? entityManager.find( entityClass, reference.getPk() ) : null;
|
||
}
|
||
|
||
public Reference toReference(BaseEntity entity) {
|
||
return entity != null ? new Reference( entity.getPk() ) : null;
|
||
}
|
||
}
|
||
|
||
@Mapper(componentModel = "cdi", uses = ReferenceMapper.class )
|
||
public interface CarMapper {
|
||
|
||
Car carDtoToCar(CarDto carDto);
|
||
}
|
||
----
|
||
====
|
||
|
||
MapStruct will then generate something like this:
|
||
|
||
.Generated code
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
//GENERATED CODE
|
||
@ApplicationScoped
|
||
public class CarMapperImpl implements CarMapper {
|
||
|
||
@Inject
|
||
private ReferenceMapper referenceMapper;
|
||
|
||
@Override
|
||
public Car carDtoToCar(CarDto carDto) {
|
||
if ( carDto == null ) {
|
||
return null;
|
||
}
|
||
|
||
Car car = new Car();
|
||
|
||
car.setOwner( referenceMapper.resolve( carDto.getOwner(), Owner.class ) );
|
||
// ...
|
||
|
||
return car;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
[[passing-context]]
|
||
=== Passing context or state objects to custom methods
|
||
|
||
Additional _context_ or _state_ information can be passed through generated mapping methods to custom methods with `@Context` parameters. Such parameters are passed to other mapping methods, `@ObjectFactory` methods (see <<object-factories>>) or `@BeforeMapping` / `@AfterMapping` methods (see <<customizing-mappings-with-before-and-after>>) when applicable and can thus be used in custom code.
|
||
|
||
`@Context` parameters are searched for `@ObjectFactory` methods, which are called on the provided context parameter value if applicable.
|
||
|
||
`@Context` parameters are also searched for `@BeforeMapping` / `@AfterMapping` methods, which are called on the provided context parameter value if applicable.
|
||
|
||
*Note:* no `null` checks are performed before calling before/after mapping methods on context parameters. The caller needs to make sure that `null` is not passed in that case.
|
||
|
||
For generated code to call a method that is declared with `@Context` parameters, the declaration of the mapping method being generated needs to contain at least those (or assignable) `@Context` parameters as well. The generated code will not create new instances of missing `@Context` parameters nor will it pass a literal `null` instead.
|
||
|
||
.Using `@Context` parameters for passing data down to hand-written property mapping methods
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public abstract CarDto toCar(Car car, @Context Locale translationLocale);
|
||
|
||
protected OwnerManualDto translateOwnerManual(OwnerManual ownerManual, @Context Locale locale) {
|
||
// manually implemented logic to translate the OwnerManual with the given Locale
|
||
}
|
||
----
|
||
====
|
||
|
||
MapStruct will then generate something like this:
|
||
|
||
.Generated code
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
//GENERATED CODE
|
||
public CarDto toCar(Car car, Locale translationLocale) {
|
||
if ( car == null ) {
|
||
return null;
|
||
}
|
||
|
||
CarDto carDto = new CarDto();
|
||
|
||
carDto.setOwnerManual( translateOwnerManual( car.getOwnerManual(), translationLocale );
|
||
// more generated mapping code
|
||
|
||
return carDto;
|
||
}
|
||
----
|
||
====
|
||
|
||
|
||
[[mapping-method-resolution]]
|
||
=== Mapping method resolution
|
||
|
||
When mapping a property from one type to another, MapStruct looks for the most specific method which maps the source type into the target type. The method may either be declared on the same mapper interface or on another mapper which is registered via `@Mapper#uses()`. The same applies for factory methods (see <<object-factories>>).
|
||
|
||
The algorithm for finding a mapping or factory method resembles Java's method resolution algorithm as much as possible. In particular, methods with a more specific source type will take precedence (e.g. if there are two methods, one which maps the searched source type, and another one which maps a super-type of the same). In case more than one most-specific method is found, an error will be raised.
|
||
|
||
[TIP]
|
||
====
|
||
When working with JAXB, e.g. when converting a `String` to a corresponding `JAXBElement<String>`, MapStruct will take the `scope` and `name` attributes of `@XmlElementDecl` annotations into account when looking for a mapping method. This makes sure that the created `JAXBElement` instances will have the right QNAME value. You can find a test which maps JAXB objects https://github.com/mapstruct/mapstruct/blob/{mapstructVersion}/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java[here].
|
||
====
|
||
|
||
[[selection-based-on-qualifiers]]
|
||
=== Mapping method selection based on qualifiers
|
||
|
||
In many occasions one requires mapping methods with the same method signature (apart from the name) that have different behavior.
|
||
MapStruct has a handy mechanism to deal with such situations: `@Qualifier` (`org.mapstruct.Qualifier`).
|
||
A ‘qualifier’ is a custom annotation that the user can write, ‘stick onto’ a mapping method which is included as used mapper
|
||
and can be referred to in a bean property mapping, iterable mapping or map mapping.
|
||
Multiple qualifiers can be ‘stuck onto’ a method and mapping.
|
||
|
||
So, let's say there is a hand-written method to map titles with a `String` return type and `String` argument amongst many other referenced mappers with the same `String` return type - `String` argument signature:
|
||
|
||
.Several mapping methods with identical source and target types
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public class Titles {
|
||
|
||
public String translateTitleEG(String title) {
|
||
// some mapping logic
|
||
}
|
||
|
||
public String translateTitleGE(String title) {
|
||
// some mapping logic
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
And a mapper using this handwritten mapper, in which source and target have a property 'title' that should be mapped:
|
||
|
||
.Mapper causing an ambiguous mapping method error
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper( uses = Titles.class )
|
||
public interface MovieMapper {
|
||
|
||
GermanRelease toGerman( OriginalRelease movies );
|
||
|
||
}
|
||
----
|
||
====
|
||
|
||
Without the use of qualifiers, this would result in an ambiguous mapping method error, because 2 qualifying methods are found (`translateTitleEG`, `translateTitleGE`) and MapStruct would not have a hint which one to choose.
|
||
|
||
Enter the qualifier approach:
|
||
|
||
.Declaring a qualifier type
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
import org.mapstruct.Qualifier;
|
||
|
||
@Qualifier
|
||
@Target(ElementType.TYPE)
|
||
@Retention(RetentionPolicy.CLASS)
|
||
public @interface TitleTranslator {
|
||
}
|
||
----
|
||
====
|
||
|
||
And, some qualifiers to indicate which translator to use to map from source language to target language:
|
||
|
||
.Declaring qualifier types for mapping methods
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
import org.mapstruct.Qualifier;
|
||
|
||
@Qualifier
|
||
@Target(ElementType.METHOD)
|
||
@Retention(RetentionPolicy.CLASS)
|
||
public @interface EnglishToGerman {
|
||
}
|
||
----
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
import org.mapstruct.Qualifier;
|
||
|
||
@Qualifier
|
||
@Target(ElementType.METHOD)
|
||
@Retention(RetentionPolicy.CLASS)
|
||
public @interface GermanToEnglish {
|
||
}
|
||
----
|
||
====
|
||
|
||
Please take note of the target `TitleTranslator` on type level, `EnglishToGerman`, `GermanToEnglish` on method level!
|
||
|
||
Then, using the qualifiers, the mapping could look like this:
|
||
|
||
.Mapper using qualifiers
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper( uses = Titles.class )
|
||
public interface MovieMapper {
|
||
|
||
@Mapping( target = "title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } )
|
||
GermanRelease toGerman( OriginalRelease movies );
|
||
|
||
}
|
||
----
|
||
====
|
||
|
||
.Custom mapper qualifying the methods it provides
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@TitleTranslator
|
||
public class Titles {
|
||
|
||
@EnglishToGerman
|
||
public String translateTitleEG(String title) {
|
||
// some mapping logic
|
||
}
|
||
|
||
@GermanToEnglish
|
||
public String translateTitleGE(String title) {
|
||
// some mapping logic
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
[WARNING]
|
||
====
|
||
Please make sure the used retention policy equals retention policy `CLASS` (`@Retention(RetentionPolicy.CLASS)`).
|
||
====
|
||
|
||
[WARNING]
|
||
====
|
||
A class / method annotated with a qualifier will not qualify anymore for mappings that do not have the `qualifiedBy` element.
|
||
====
|
||
|
||
[TIP]
|
||
====
|
||
The same mechanism is also present on bean mappings: `@BeanMapping#qualifiedBy`: it selects the factory method marked with the indicated qualifier.
|
||
====
|
||
|
||
In many occasions, declaring a new annotation to aid the selection process can be too much for what you try to achieve. For those situations, MapStruct has the `@Named` annotation. This annotation is a pre-defined qualifier (annotated with `@Qualifier` itself) and can be used to name a Mapper or, more directly a mapping method by means of its value. The same example above would look like:
|
||
|
||
.Custom mapper, annotating the methods to qualify by means of `@Named`
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Named("TitleTranslator")
|
||
public class Titles {
|
||
|
||
@Named("EnglishToGerman")
|
||
public String translateTitleEG(String title) {
|
||
// some mapping logic
|
||
}
|
||
|
||
@Named("GermanToEnglish")
|
||
public String translateTitleGE(String title) {
|
||
// some mapping logic
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
.Mapper using named
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper( uses = Titles.class )
|
||
public interface MovieMapper {
|
||
|
||
@Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } )
|
||
GermanRelease toGerman( OriginalRelease movies );
|
||
|
||
}
|
||
----
|
||
====
|
||
|
||
[WARNING]
|
||
====
|
||
Although the used mechanism is the same, the user has to be a bit more careful. Refactoring the name of a defined qualifier in an IDE will neatly refactor all other occurrences as well. This is obviously not the case for changing a name.
|
||
====
|
||
|
||
|
||
[[mapping-collections]]
|
||
== Mapping collections
|
||
|
||
The mapping of collection types (`List`, `Set` etc.) is done in the same way as mapping bean types, i.e. by defining mapping methods with the required source and target types in a mapper interface. MapStruct supports a wide range of iterable types from the http://docs.oracle.com/javase/tutorial/collections/intro/index.html[Java Collection Framework].
|
||
|
||
The generated code will contain a loop which iterates over the source collection, converts each element and puts it into the target collection. If a mapping method for the collection element types is found in the given mapper or the mapper it uses, this method is invoked to perform the element conversion. Alternatively, if an implicit conversion for the source and target element types exists, this conversion routine will be invoked. The following shows an example:
|
||
|
||
.Mapper with collection mapping methods
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface CarMapper {
|
||
|
||
Set<String> integerSetToStringSet(Set<Integer> integers);
|
||
|
||
List<CarDto> carsToCarDtos(List<Car> cars);
|
||
|
||
CarDto carToCarDto(Car car);
|
||
}
|
||
----
|
||
====
|
||
|
||
The generated implementation of the `integerSetToStringSet` performs the conversion from `Integer` to `String` for each element, while the generated `carsToCarDtos()` method invokes the `carToCarDto()` method for each contained element as shown in the following:
|
||
|
||
.Generated collection mapping methods
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
//GENERATED CODE
|
||
@Override
|
||
public Set<String> integerSetToStringSet(Set<Integer> integers) {
|
||
if ( integers == null ) {
|
||
return null;
|
||
}
|
||
|
||
Set<String> set = new HashSet<String>();
|
||
|
||
for ( Integer integer : integers ) {
|
||
set.add( String.valueOf( integer ) );
|
||
}
|
||
|
||
return set;
|
||
}
|
||
|
||
@Override
|
||
public List<CarDto> carsToCarDtos(List<Car> cars) {
|
||
if ( cars == null ) {
|
||
return null;
|
||
}
|
||
|
||
List<CarDto> list = new ArrayList<CarDto>();
|
||
|
||
for ( Car car : cars ) {
|
||
list.add( carToCarDto( car ) );
|
||
}
|
||
|
||
return list;
|
||
}
|
||
----
|
||
====
|
||
|
||
Note that MapStruct will look for a collection mapping method with matching parameter and return type, when mapping a collection-typed attribute of a bean, e.g. from `Car#passengers` (of type `List<Person>`) to `CarDto#passengers` (of type `List<PersonDto>`).
|
||
|
||
.Usage of collection mapping method to map a bean property
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
//GENERATED CODE
|
||
carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) );
|
||
...
|
||
----
|
||
====
|
||
|
||
Some frameworks and libraries only expose JavaBeans getters but no setters for collection-typed properties. Types generated from an XML schema using JAXB adhere to this pattern by default. In this case the generated code for mapping such a property invokes its getter and adds all the mapped elements:
|
||
|
||
.Usage of an adding method for collection mapping
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
//GENERATED CODE
|
||
carDto.getPassengers().addAll( personsToPersonDtos( car.getPassengers() ) );
|
||
...
|
||
----
|
||
====
|
||
|
||
[WARNING]
|
||
====
|
||
It is not allowed to declare mapping methods with an iterable source and a non-iterable target or the other way around. An error will be raised when detecting this situation.
|
||
====
|
||
|
||
[[mapping-maps]]
|
||
=== Mapping maps
|
||
|
||
Also map-based mapping methods are supported. The following shows an example:
|
||
|
||
.Map mapping method
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public interface SourceTargetMapper {
|
||
|
||
@MapMapping(valueDateFormat = "dd.MM.yyyy")
|
||
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
|
||
}
|
||
----
|
||
====
|
||
|
||
Similar to iterable mappings, the generated code will iterate through the source map, convert each value and key (either by means of an implicit conversion or by invoking another mapping method) and put them into the target map:
|
||
|
||
.Generated implementation of map mapping method
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
//GENERATED CODE
|
||
@Override
|
||
public Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source) {
|
||
if ( source == null ) {
|
||
return null;
|
||
}
|
||
|
||
Map<Long, Date> map = new HashMap<Long, Date>();
|
||
|
||
for ( Map.Entry<String, String> entry : source.entrySet() ) {
|
||
|
||
Long key = Long.parseLong( entry.getKey() );
|
||
Date value;
|
||
try {
|
||
value = new SimpleDateFormat( "dd.MM.yyyy" ).parse( entry.getValue() );
|
||
}
|
||
catch( ParseException e ) {
|
||
throw new RuntimeException( e );
|
||
}
|
||
|
||
map.put( key, value );
|
||
}
|
||
|
||
return map;
|
||
}
|
||
----
|
||
====
|
||
|
||
[[collection-mapping-strategies]]
|
||
=== Collection mapping strategies
|
||
|
||
MapStruct has a `CollectionMappingStrategy`, with the possible values: `ACCESSOR_ONLY`, `SETTER_PREFERRED`, `ADDER_PREFERRED` and `TARGET_IMMUTABLE`.
|
||
|
||
In the table below, the dash `-` indicates a property name. Next, the trailing `s` indicates the plural form. The table explains the options and how they are applied to the presence/absense of a `set-s`, `add-` and / or `get-s` method on the target object:
|
||
|
||
.Collection mapping strategy options
|
||
|===
|
||
|Option|Only target set-s Available|Only target add- Available|Both set-s / add- Available|No set-s / add- Available|Existing Target(`@TargetType`)
|
||
|
||
|`ACCESSOR_ONLY`
|
||
|set-s
|
||
|get-s
|
||
|set-s
|
||
|get-s
|
||
|get-s
|
||
|
||
|`SETTER_PREFERRED`
|
||
|set-s
|
||
|add-
|
||
|set-s
|
||
|get-s
|
||
|get-s
|
||
|
||
|`ADDER_PREFERRED`
|
||
|set-s
|
||
|add-
|
||
|add-
|
||
|get-s
|
||
|get-s
|
||
|
||
|`TARGET_IMMUTABLE`
|
||
|set-s
|
||
|exception
|
||
|set-s
|
||
|exception
|
||
|set-s
|
||
|===
|
||
|
||
Some background: An `adder` method is typically used in case of http://www.eclipse.org/webtools/dali/[generated (JPA) entities], to add a single element (entity) to an underlying collection. Invoking the adder establishes a parent-child relation between parent - the bean (entity) on which the adder is invoked - and its child(ren), the elements (entities) in the collection. To find the appropriate `adder`, MapStruct will try to make a match between the generic parameter type of the underlying collection and the single argument of a candidate `adder`. When there are more candidates, the plural `setter` / `getter` name is converted to singular and will be used in addition to make a match.
|
||
|
||
The option `DEFAULT` should not be used explicitly. It is used to distinguish between an explicit user desire to override the default in a `@MapperConfig` from the implicit Mapstruct choice in a `@Mapper`. The option `DEFAULT` is synonymous to `ACCESSOR_ONLY`.
|
||
|
||
[TIP]
|
||
====
|
||
When working with an `adder` method and JPA entities, Mapstruct assumes that the target collections are initialized with a collection implementation (e.g. an `ArrayList`). You can use factories to create a new target entity with intialized collections instead of Mapstruct creating the target entity by its constructor.
|
||
====
|
||
|
||
[[implementation-types-for-collection-mappings]]
|
||
=== Implementation types used for collection mappings
|
||
|
||
When an iterable or map mapping method declares an interface type as return type, one of its implementation types will be instantiated in the generated code. The following table shows the supported interface types and their corresponding implementation types as instantiated in the generated code:
|
||
|
||
.Collection mapping implementation types
|
||
|===
|
||
|Interface type|Implementation type
|
||
|
||
|`Iterable`|`ArrayList`
|
||
|
||
|`Collection`|`ArrayList`
|
||
|
||
|`List`|`ArrayList`
|
||
|
||
|`Set`|`HashSet`
|
||
|
||
|`SortedSet`|`TreeSet`
|
||
|
||
|`NavigableSet`|`TreeSet`
|
||
|
||
|`Map`|`HashMap`
|
||
|
||
|`SortedMap`|`TreeMap`
|
||
|
||
|`NavigableMap`|`TreeMap`
|
||
|
||
|`ConcurrentMap`|`ConcurrentHashMap`
|
||
|`ConcurrentNavigableMap`|`ConcurrentSkipListMap`
|
||
|===
|
||
|
||
include::mapping-streams.asciidoc[]
|
||
|
||
[[mapping-enum-types]]
|
||
== Mapping Values
|
||
|
||
=== Mapping enum types
|
||
|
||
MapStruct supports the generation of methods which map one Java enum type into another.
|
||
|
||
By default, each constant from the source enum is mapped to a constant with the same name in the target enum type. If required, a constant from the source enum may be mapped to a constant with another name with help of the `@ValueMapping` annotation. Several constants from the source enum can be mapped to the same constant in the target type.
|
||
|
||
The following shows an example:
|
||
|
||
.Enum mapping method
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@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);
|
||
}
|
||
----
|
||
====
|
||
|
||
.Enum mapping method result
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
// 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_;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
By default an error will be raised by MapStruct in case a constant of the source enum type does not have a corresponding constant with the same name in the target type and also is not mapped to another constant via `@ValueMapping`. This ensures that all constants are mapped in a safe and predictable manner. The generated
|
||
mapping method will throw an IllegalStateException if for some reason an unrecognized source value occurs.
|
||
|
||
MapStruct also has a mechanism for mapping any remaining (unspecified) mappings to a default. This can be used only once in a set of value mappings. It comes in two flavors: `<ANY_REMAINING>` and `<ANY_UNMAPPED>`.
|
||
|
||
In case of source `<ANY_REMAINING>` MapStruct will continue to map a source enum constant to a target enum constant with the same name. The remainder of the source enum constants will be mapped to the target specified in the `@ValueMapping` with `<ANY_REMAINING>` source.
|
||
|
||
MapStruct will *not* attempt such name based mapping for `<ANY_UNMAPPED>` and directly apply the target specified in the `@ValueMapping` with `<ANY_UNMAPPED>` source to the remainder.
|
||
|
||
MapStruct is able to handle `null` sources and `null` targets by means of the `<NULL>` keyword.
|
||
|
||
[TIP]
|
||
====
|
||
Constants for `<ANY_REMAINING>`, `<ANY_UNMAPPED>` and `<NULL>` are available in the `MappingConstants` class.
|
||
====
|
||
|
||
Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`.
|
||
|
||
.Enum mapping method, <NULL> and <ANY_REMAINING>
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface SpecialOrderMapper {
|
||
|
||
SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class );
|
||
|
||
@ValueMappings({
|
||
@ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
|
||
@ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
|
||
@ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" )
|
||
})
|
||
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
|
||
}
|
||
----
|
||
====
|
||
|
||
.Enum mapping method result, <NULL> and <ANY_REMAINING>
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
// GENERATED CODE
|
||
public class SpecialOrderMapperImpl implements SpecialOrderMapper {
|
||
|
||
@Override
|
||
public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
|
||
if ( orderType == null ) {
|
||
return ExternalOrderType.DEFAULT;
|
||
}
|
||
|
||
ExternalOrderType externalOrderType_;
|
||
|
||
switch ( orderType ) {
|
||
case STANDARD: externalOrderType_ = null;
|
||
break;
|
||
case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL;
|
||
break;
|
||
case B2B: externalOrderType_ = ExternalOrderType.B2B;
|
||
break;
|
||
default: externalOrderType_ = ExternalOrderType.SPECIAL;
|
||
}
|
||
|
||
return externalOrderType_;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
*Note:* MapStruct would have refrained from mapping the `RETAIL` and `B2B` when `<ANY_UNMAPPED>` was used instead of `<ANY_REMAINING>`.
|
||
|
||
|
||
[WARNING]
|
||
====
|
||
The mapping of enum to enum via the `@Mapping` annotation is *DEPRECATED*. It will be removed from future versions of MapStruct. Please adapt existing enum mapping methods to make use of `@ValueMapping` instead.
|
||
====
|
||
|
||
|
||
[[object-factories]]
|
||
== Object factories
|
||
|
||
By default, the generated code for mapping one bean type into another or updating a bean will call the default constructor to instantiate the target type.
|
||
|
||
Alternatively you can plug in custom object factories which will be invoked to obtain instances of the target type. One use case for this is JAXB which creates `ObjectFactory` classes for obtaining new instances of schema types.
|
||
|
||
To make use of custom factories register them via `@Mapper#uses()` as described in <<invoking-other-mappers>>, or implement them directly in your mapper. When creating the target object of a bean mapping, MapStruct will look for a parameterless method, a method annotated with `@ObjectFactory`, or a method with only one `@TargetType` parameter that returns the required target type and invoke this method instead of calling the default constructor:
|
||
|
||
.Custom object factories
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public class DtoFactory {
|
||
|
||
public CarDto createCarDto() {
|
||
return // ... custom factory logic
|
||
}
|
||
}
|
||
----
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public class EntityFactory {
|
||
|
||
public <T extends BaseEntity> T createEntity(@TargetType Class<T> entityClass) {
|
||
return // ... custom factory logic
|
||
}
|
||
}
|
||
----
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper(uses= { DtoFactory.class, EntityFactory.class } )
|
||
public interface CarMapper {
|
||
|
||
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
|
||
|
||
CarDto carToCarDto(Car car);
|
||
|
||
Car carDtoToCar(CarDto carDto);
|
||
}
|
||
----
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
//GENERATED CODE
|
||
public class CarMapperImpl implements CarMapper {
|
||
|
||
private final DtoFactory dtoFactory = new DtoFactory();
|
||
|
||
private final EntityFactory entityFactory = new EntityFactory();
|
||
|
||
@Override
|
||
public CarDto carToCarDto(Car car) {
|
||
if ( car == null ) {
|
||
return null;
|
||
}
|
||
|
||
CarDto carDto = dtoFactory.createCarDto();
|
||
|
||
//map properties...
|
||
|
||
return carDto;
|
||
}
|
||
|
||
@Override
|
||
public Car carDtoToCar(CarDto carDto) {
|
||
if ( carDto == null ) {
|
||
return null;
|
||
}
|
||
|
||
Car car = entityFactory.createEntity( Car.class );
|
||
|
||
//map properties...
|
||
|
||
return car;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
.Custom object factories with update methods
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper(uses = { DtoFactory.class, EntityFactory.class, CarMapper.class } )
|
||
public interface OwnerMapper {
|
||
|
||
OwnerMapper INSTANCE = Mappers.getMapper( OwnerMapper.class );
|
||
|
||
void updateOwnerDto(Owner owner, @MappingTarget OwnerDto ownerDto);
|
||
|
||
void updateOwner(OwnerDto ownerDto, @MappingTarget Owner owner);
|
||
}
|
||
----
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
//GENERATED CODE
|
||
public class OwnerMapperImpl implements OwnerMapper {
|
||
|
||
private final DtoFactory dtoFactory = new DtoFactory();
|
||
|
||
private final EntityFactory entityFactory = new EntityFactory();
|
||
|
||
private final OwnerMapper ownerMapper = Mappers.getMapper( OwnerMapper.class );
|
||
|
||
@Override
|
||
public void updateOwnerDto(Owner owner, @MappingTarget OwnerDto ownerDto) {
|
||
if ( owner == null ) {
|
||
return;
|
||
}
|
||
|
||
if ( owner.getCar() != null ) {
|
||
if ( ownerDto.getCar() == null ) {
|
||
ownerDto.setCar( dtoFactory.createCarDto() );
|
||
}
|
||
// update car within ownerDto
|
||
}
|
||
else {
|
||
ownerDto.setCar( null );
|
||
}
|
||
|
||
// updating other properties
|
||
}
|
||
|
||
@Override
|
||
public void updateOwner(OwnerDto ownerDto, @MappingTarget Owner owner) {
|
||
if ( ownerDto == null ) {
|
||
return;
|
||
}
|
||
|
||
if ( ownerDto.getCar() != null ) {
|
||
if ( owner.getCar() == null ) {
|
||
owner.setCar( entityFactory.createEntity( Car.class ) );
|
||
}
|
||
// update car within owner
|
||
}
|
||
else {
|
||
owner.setCar( null );
|
||
}
|
||
|
||
// updating other properties
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
In addition, annotating a factory method with `@ObjectFactory` lets you gain access to the mapping sources.
|
||
Source objects can be added as parameters in the same way as for mapping method. The `@ObjectFactory`
|
||
annotation is necessary to let MapStruct know that the given method is only a factory method.
|
||
|
||
.Custom object factories with `@ObjectFactory`
|
||
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public class DtoFactory {
|
||
|
||
@ObjectFactory
|
||
public CarDto createCarDto(Car car) {
|
||
return // ... custom factory logic
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
|
||
== Advanced mapping options
|
||
This chapter describes several advanced options which allow to fine-tune the behavior of the generated mapping code as needed.
|
||
|
||
[[default-values-and-constants]]
|
||
=== Default values and constants
|
||
|
||
Default values can be specified to set a predefined value to a target property if the corresponding source property is `null`. Constants can be specified to set such a predefined value in any case. Default values and constants are specified as String values. When the target type is a primitive or a boxed type, the String value is taken literal. Bit / octal / decimal / hex patterns are allowed in such case as long as they are a valid literal.
|
||
In all other cases, constant or default values are subject to type conversion either via built-in conversions or the invocation of other mapping methods in order to match the type required by the target property.
|
||
|
||
A mapping with a constant must not include a reference to a source property. The following example shows some mappings using default values and constants:
|
||
|
||
.Mapping method with default values and constants
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@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);
|
||
}
|
||
----
|
||
====
|
||
|
||
If `s.getStringProp() == null`, then the target property `stringProperty` will be set to `"undefined"` instead of applying the value from `s.getStringProp()`. If `s.getLongProperty() == null`, then the target property `longProperty` will be set to `-1`.
|
||
The String `"Constant Value"` is set as is to the target property `stringConstant`. The value `"3001"` is type-converted to the `Long` (wrapper) class of target property `longWrapperConstant`. Date properties also require a date format. The constant `"jack-jill-tom"` demonstrates how the hand-written class `StringListMapper` is invoked to map the dash-separated list into a `List<String>`.
|
||
|
||
[[expressions]]
|
||
=== Expressions
|
||
|
||
By means of Expressions it will be possible to include constructs from a number of languages.
|
||
|
||
Currently only Java is supported as a language. This feature is e.g. useful to invoke constructors. The entire source object is available for usage in the expression. Care should be taken to insert only valid Java code: MapStruct will not validate the expression at generation-time, but errors will show up in the generated classes during compilation.
|
||
|
||
The example below demonstrates how two source properties can be mapped to one target:
|
||
|
||
.Mapping method using an expression
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface SourceTargetMapper {
|
||
|
||
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
|
||
|
||
@Mapping(target = "timeAndFormat",
|
||
expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
|
||
Target sourceToTarget(Source s);
|
||
}
|
||
----
|
||
====
|
||
|
||
The example demonstrates how the source properties `time` and `format` are composed into one target property `TimeAndFormat`. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the `TimeAndFormat` class (unless it's used otherwise explicitly in the `SourceTargetMapper`). This can be resolved by defining `imports` on the `@Mapper` annotation.
|
||
|
||
.Declaring an import
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
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);
|
||
}
|
||
----
|
||
====
|
||
|
||
[[default-expressions]]
|
||
=== Default Expressions
|
||
|
||
Default expressions are a combination of default values and expressions. They will only be used when the source attribute is `null`.
|
||
|
||
The same warnings and restrictions apply to default expressions that apply to expressions. Only Java is supported, and MapStruct will not validate the expression at generation-time.
|
||
|
||
The example below demonstrates how two source properties can be mapped to one target:
|
||
|
||
.Mapping method using a default expression
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
imports java.util.UUID;
|
||
|
||
@Mapper( imports = UUID.class )
|
||
public interface SourceTargetMapper {
|
||
|
||
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
|
||
|
||
@Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )")
|
||
Target sourceToTarget(Source s);
|
||
}
|
||
----
|
||
====
|
||
|
||
The example demonstrates how to use defaultExpression to set an `ID` field if the source field is null, this could be used to take the existing `sourceId` from the source object if it is set, or create a new `Id` if it isn't. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the `UUID` class (unless it’s used otherwise explicitly in the `SourceTargetMapper`). This can be resolved by defining imports on the @Mapper annotation ((see <<expressions>>).
|
||
|
||
[[determining-result-type]]
|
||
=== Determining the result type
|
||
|
||
When result types have an inheritance relation, selecting either mapping method (`@Mapping`) or a factory method (`@BeanMapping`) can become ambiguous. Suppose an Apple and a Banana, which are both specializations of Fruit.
|
||
|
||
.Specifying the result type of a bean mapping method
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper( uses = FruitFactory.class )
|
||
public interface FruitMapper {
|
||
|
||
@BeanMapping( resultType = Apple.class )
|
||
Fruit map( FruitDto source );
|
||
|
||
}
|
||
----
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public class FruitFactory {
|
||
|
||
public Apple createApple() {
|
||
return new Apple( "Apple" );
|
||
}
|
||
|
||
public Banana createBanana() {
|
||
return new Banana( "Banana" );
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
So, which `Fruit` must be factorized in the mapping method `Fruit map(FruitDto source);`? A `Banana` or an `Apple`? Here's were the `@BeanMapping#resultType` comes in handy. It controls the factory method to select, or in absence of a factory method, the return type to create.
|
||
|
||
[TIP]
|
||
====
|
||
The same mechanism is present on mapping: `@Mapping#resultType` and works like you expect it would: it selects the mapping method with the desired result type when present.
|
||
====
|
||
|
||
[TIP]
|
||
====
|
||
The mechanism is also present on iterable mapping and map mapping. `@IterableMapping#elementTargetType` is used to select the mapping method with the desired element in the resulting `Iterable`. For the `@MapMapping` a similar purpose is served by means of `#MapMapping#keyTargetType` and `MapMapping#valueTargetType`.
|
||
====
|
||
|
||
[[mapping-result-for-null-arguments]]
|
||
=== Controlling mapping result for 'null' arguments
|
||
|
||
MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. By default `null` will be returned.
|
||
|
||
However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MappingConfig`, the mapping result can be altered to return empty *default* values. This means for:
|
||
|
||
* *Bean mappings*: an 'empty' target bean will be returned, with the exception of constants and expressions, they will be populated when present.
|
||
* *Iterables / Arrays*: an empty iterable will be returned.
|
||
* *Maps*: an empty map will be returned.
|
||
|
||
The strategy works in a hierarchical fashion. Setting `nullValueMappingStrategy` on mapping method level will override `@Mapper#nullValueMappingStrategy`, and `@Mapper#nullValueMappingStrategy` will override `@MappingConfig#nullValueMappingStrategy`.
|
||
|
||
|
||
[[mapping-result-for-null-properties]]
|
||
=== Controlling mapping result for 'null' properties in bean mappings (update mapping methods only).
|
||
|
||
MapStruct offers control over the property to set in an `@MappingTarget` annotated target bean when the source property equals `null` or the presence check method results in 'absent'.
|
||
|
||
By default the target property will be set to null.
|
||
|
||
However:
|
||
|
||
1. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MappingConfig`, the mapping result can be altered to return *default* values.
|
||
For `List` MapStruct generates an `ArrayList`, for `Map` a `HashMap`, for arrays an empty array, for `String` `""` and for primitive / boxed types a representation of `false` or `0`.
|
||
For all other objects an new instance is created. Please note that a default constructor is required. If not available, use the `@Mapping#defaultValue`.
|
||
|
||
2. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MappingConfig`, the mapping result will be equal to the original value of the `@MappingTarget` annotated target.
|
||
|
||
The strategy works in a hierarchical fashion. Setting `Mapping#nullValuePropertyMappingStrategy` on mapping level will override `nullValuePropertyMappingStrategy` on mapping method level will override `@Mapper#nullValuePropertyMappingStrategy`, and `@Mapper#nullValuePropertyMappingStrategy` will override `@MappingConfig#nullValuePropertyMappingStrategy`.
|
||
|
||
[NOTE]
|
||
====
|
||
Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor see `CollectionMappingStrategy`, MapStruct will always generate a source property
|
||
null check, regardless the value of the `NullValuePropertyMappingStrategy` to avoid addition of `null` to the target collection or map. Since the target is assumed to be initialised this strategy will not be applied.
|
||
====
|
||
|
||
[TIP]
|
||
====
|
||
`NullValuePropertyMappingStrategy` also applies when the presense checker returns `not present`.
|
||
====
|
||
|
||
[[checking-source-property-for-null-arguments]]
|
||
=== Controlling checking result for 'null' properties in bean mapping
|
||
|
||
MapStruct offers control over when to generate a `null` check. By default (`nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION`) a `null` check will be generated for:
|
||
|
||
* direct setting of source value to target value when target is primitive and source is not.
|
||
* applying type conversion and then:
|
||
.. calling the setter on the target.
|
||
.. calling another type conversion and subsequently calling the setter on the target.
|
||
.. calling a mapping method and subsequently calling the setter on the target.
|
||
|
||
First calling a mapping method on the source property is not protected by a null check. Therefor generated mapping methods will do a null check prior to carrying out mapping on a source property. Handwritten mapping methods must take care of null value checking. They have the possibility to add 'meaning' to `null`. For instance: mapping `null` to a default value.
|
||
|
||
The option `nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS` will always include a null check when source is non primitive, unless a source presence checker is defined on the source bean.
|
||
|
||
The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MappingConfig#nullValueCheckStrategy`.
|
||
|
||
[[source-presence-check]]
|
||
=== Source presence checking
|
||
Some frameworks generate bean properties that have a source presence checker. Often this is in the form of a method `hasXYZ`, `XYZ` being a property on the source bean in a bean mapping method. MapStruct will call this `hasXYZ` instead of performing a `null` check when it finds such `hasXYZ` method.
|
||
|
||
[TIP]
|
||
====
|
||
The source presence checker name can be changed in the MapStruct service provider interface (SPI). It can also be deactivated in this way.
|
||
====
|
||
|
||
[NOTE]
|
||
====
|
||
Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor see `CollectionMappingStrategy`, MapStruct will always generate a source property
|
||
null check, regardless the value of the `NullValueheckStrategy` to avoid addition of `null` to the target collection or map.
|
||
====
|
||
[[exceptions]]
|
||
=== Exceptions
|
||
|
||
Calling applications may require handling of exceptions when calling a mapping method. These exceptions could be thrown by hand-written logic and by the generated built-in mapping methods or type-conversions of MapStruct. When the calling application requires handling of exceptions, a throws clause can be defined in the mapping method:
|
||
|
||
.Mapper using custom method declaring checked exception
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper(uses = HandWritten.class)
|
||
public interface CarMapper {
|
||
|
||
CarDto carToCarDto(Car car) throws GearException;
|
||
}
|
||
----
|
||
====
|
||
|
||
The hand written logic might look like this:
|
||
|
||
.Custom mapping method declaring checked exception
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
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];
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
MapStruct now, wraps the `FatalException` in a `try-catch` block and rethrows an unchecked `RuntimeException`. MapStruct delegates handling of the `GearException` to the application logic because it is defined as throws clause in the `carToCarDto` method:
|
||
|
||
.try-catch block in generated implementation
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
// GENERATED CODE
|
||
@Override
|
||
public CarDto carToCarDto(Car car) throws GearException {
|
||
if ( car == null ) {
|
||
return null;
|
||
}
|
||
|
||
CarDto carDto = new CarDto();
|
||
try {
|
||
carDto.setGear( handWritten.toGear( car.getGear() ) );
|
||
}
|
||
catch ( FatalException e ) {
|
||
throw new RuntimeException( e );
|
||
}
|
||
|
||
return carDto;
|
||
}
|
||
----
|
||
====
|
||
|
||
Some **notes** on null checks. MapStruct does provide null checking only when required: when applying type-conversions or constructing a new type by invoking its constructor. This means that the user is responsible in hand-written code for returning valid non-null objects. Also null objects can be handed to hand-written code, since MapStruct does not want to make assumptions on the meaning assigned by the user to a null object. Hand-written code has to deal with this.
|
||
|
||
== Reusing mapping configurations
|
||
|
||
This chapter discusses different means of reusing mapping configurations for several mapping methods: "inheritance" of configuration from other methods and sharing central configuration between multiple mapper types.
|
||
|
||
[[mapping-configuration-inheritance]]
|
||
=== Mapping configuration inheritance
|
||
|
||
Method-level configuration annotations such as `@Mapping`, `@BeanMapping`, `@IterableMapping`, etc., can be *inherited* from one mapping method to a *similar* method using the annotation `@InheritConfiguration`:
|
||
|
||
.Update method inheriting its configuration
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface CarMapper {
|
||
|
||
@Mapping(target = "numberOfSeats", source = "seatCount")
|
||
Car carDtoToCar(CarDto car);
|
||
|
||
@InheritConfiguration
|
||
void carDtoIntoCar(CarDto carDto, @MappingTarget Car car);
|
||
}
|
||
----
|
||
====
|
||
|
||
The example above declares a mapping method `carDtoToCar()` with a configuration to define how the property `numberOfSeats` in the type `Car` shall be mapped. The update method that performs the mapping on an existing instance of `Car` needs the same configuration to successfully map all properties. Declaring `@InheritConfiguration` on the method lets MapStruct search for inheritance candidates to apply the annotations of the method that is inherited from.
|
||
|
||
One method *A* can inherit the configuration from another method *B* if all types of *A* (source types and result type) are assignable to the corresponding types of *B*.
|
||
|
||
Methods that are considered for inheritance need to be defined in the current mapper, a super class/interface, or in the shared configuration interface (as described in <<shared-configurations>>).
|
||
|
||
In case more than one method is applicable as source for the inheritance, the method name must be specified within the annotation: `@InheritConfiguration( name = "carDtoToCar" )`.
|
||
|
||
A method can use `@InheritConfiguration` and override or amend the configuration by additionally applying `@Mapping`, `@BeanMapping`, etc.
|
||
|
||
[NOTE]
|
||
====
|
||
`@InheritConfiguration` cannot refer to methods in a used mapper.
|
||
====
|
||
|
||
[[inverse-mappings]]
|
||
=== Inverse mappings
|
||
|
||
In case of bi-directional mappings, e.g. from entity to DTO and from DTO to entity, the mapping rules for the forward method and the reverse method are often similar and can simply be inversed by switching `source` and `target`.
|
||
|
||
Use the annotation `@InheritInverseConfiguration` to indicate that a method shall inherit the inverse configuration of the corresponding reverse method.
|
||
|
||
.Inverse mapping method inheriting its configuration and ignoring some of them
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface CarMapper {
|
||
|
||
@Mapping(source = "numberOfSeats", target = "seatCount")
|
||
CarDto carToDto(Car car);
|
||
|
||
@InheritInverseConfiguration
|
||
@Mapping(target = "numberOfSeats", ignore = true)
|
||
Car carDtoToCar(CarDto carDto);
|
||
}
|
||
----
|
||
====
|
||
|
||
Here the `carDtoToCar()` method is the reverse mapping method for `carToDto()`. Note that any attribute mappings from `carToDto()` will be applied to the corresponding reverse mapping method as well. They are automatically reversed and copied to the method with the `@InheritInverseConfiguration` annotation.
|
||
|
||
Specific mappings from the inversed method can (optionally) be overridden by `ignore`, `expression` or `constant` in the mapping, e.g. like this: `@Mapping(target = "numberOfSeats", ignore=true)`.
|
||
|
||
A method *A* is considered a *reverse* method of a method *B*, if the result type of *A* is the *same* as the single source type of *B* and if the single source type of *A* is the *same* as the result type of *B*.
|
||
|
||
Methods that are considered for inverse inheritance need to be defined in the current mapper, a super class/interface.
|
||
|
||
If multiple methods qualify, the method from which to inherit the configuration needs to be specified using the `name` property like this: `@InheritInverseConfiguration(name = "carToDto")`.
|
||
|
||
`@InheritConfiguration` takes, in case of conflict precedence over `@InheritInverseConfiguration`.
|
||
|
||
Configurations are inherited transitively. So if method `C` defines a mapping `@Mapping( target = "x", ignore = true)`, `B` defines a mapping `@Mapping( target = "y", ignore = true)`, then if `A` inherits from `B` inherits from `C`, `A` will inherit mappings for both property `x` and `y`.
|
||
|
||
Expressions and constants are excluded (silently ignored) in `@InheritInverseConfiguration`.
|
||
|
||
Reverse mapping of nested source properties is experimental as of the 1.1.0.Beta2 release. Reverse mapping will take place automatically when the source property name and target property name are identical. Otherwise, `@Mapping` should specify both the target name and source name. In all cases, a suitable mapping method needs to be in place for the reverse mapping.
|
||
|
||
[NOTE]
|
||
====
|
||
`@InheritConfiguration` or `@InheritInverseConfiguration` cannot refer to methods in a used mapper.
|
||
====
|
||
|
||
[[shared-configurations]]
|
||
=== Shared configurations
|
||
|
||
MapStruct offers the possibility to define a shared configuration by pointing to a central interface annotated with `@MapperConfig`. For a mapper to use the shared configuration, the configuration interface needs to be defined in the `@Mapper#config` property.
|
||
|
||
The `@MapperConfig` annotation has the same attributes as the `@Mapper` annotation. Any attributes not given via `@Mapper` will be inherited from the shared configuration. Attributes specified in `@Mapper` take precedence over the attributes specified via the referenced configuration class. List properties such as `uses` are simply combined:
|
||
|
||
.Mapper configuration class and mapper using it
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@MapperConfig(
|
||
uses = CustomMapperViaMapperConfig.class,
|
||
unmappedTargetPolicy = ReportingPolicy.ERROR
|
||
)
|
||
public interface CentralConfig {
|
||
}
|
||
----
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )
|
||
// Effective configuration:
|
||
// @Mapper(
|
||
// uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },
|
||
// unmappedTargetPolicy = ReportingPolicy.ERROR
|
||
// )
|
||
public interface SourceTargetMapper {
|
||
...
|
||
}
|
||
|
||
----
|
||
====
|
||
|
||
The interface holding the `@MapperConfig` annotation may also declare *prototypes* of mapping methods that can be used to inherit method-level mapping annotations from. Such prototype methods are not meant to be implemented or used as part of the mapper API.
|
||
|
||
.Mapper configuration class with prototype methods
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@MapperConfig(
|
||
uses = CustomMapperViaMapperConfig.class,
|
||
unmappedTargetPolicy = ReportingPolicy.ERROR,
|
||
mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG
|
||
)
|
||
public interface CentralConfig {
|
||
|
||
// Not intended to be generated, but to carry inheritable mapping annotations:
|
||
@Mapping(target = "primaryKey", source = "technicalKey")
|
||
BaseEntity anyDtoToEntity(BaseDto dto);
|
||
}
|
||
----
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )
|
||
public interface SourceTargetMapper {
|
||
|
||
@Mapping(target = "numberOfSeats", source = "seatCount")
|
||
// additionally inherited from CentralConfig, because Car extends BaseEntity and CarDto extends BaseDto:
|
||
// @Mapping(target = "primaryKey", source = "technicalKey")
|
||
Car toCar(CarDto car)
|
||
}
|
||
----
|
||
====
|
||
|
||
The attributes `@Mapper#mappingInheritanceStrategy()` / `@MapperConfig#mappingInheritanceStrategy()` configure when the method-level mapping configuration annotations are inherited from prototype methods in the interface to methods in the mapper:
|
||
|
||
* `EXPLICIT` (default): the configuration will only be inherited, if the target mapping method is annotated with `@InheritConfiguration` and the source and target types are assignable to the corresponding types of the prototype method, all as described in <<mapping-configuration-inheritance>>.
|
||
* `AUTO_INHERIT_FROM_CONFIG`: the configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using `@InheritConfiguration(name = ...)` which will cause `AUTO_INHERIT_FROM_CONFIG` to be ignored.
|
||
* `AUTO_INHERIT_REVERSE_FROM_CONFIG`: the inverse configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using `@InheritInverseConfiguration(name = ...)` which will cause ``AUTO_INHERIT_REVERSE_FROM_CONFIG` to be ignored.
|
||
* `AUTO_INHERIT_ALL_FROM_CONFIG`: both the configuration and the inverse configuration will be inherited automatically. The same rules apply as for `AUTO_INHERIT_FROM_CONFIG` or `AUTO_INHERIT_REVERSE_FROM_CONFIG`.
|
||
|
||
== Customizing mappings
|
||
|
||
Sometimes it's needed to apply custom logic before or after certain mapping methods. MapStruct provides two ways for doing so: decorators which allow for a type-safe customization of specific mapping methods and the before-mapping and after-mapping lifecycle methods which allow for a generic customization of mapping methods with given source or target types.
|
||
|
||
[[customizing-mappers-using-decorators]]
|
||
=== Mapping customization with decorators
|
||
|
||
In certain cases it may be required to customize a generated mapping method, e.g. to set an additional property in the target object which can't be set by a generated method implementation. MapStruct supports this requirement using decorators.
|
||
|
||
[TIP]
|
||
When working with the component model `cdi`, use https://docs.jboss.org/cdi/spec/1.0/html/decorators.html[CDI decorators] with MapStruct mappers instead of the `@DecoratedWith` annotation described here.
|
||
|
||
To apply a decorator to a mapper class, specify it using the `@DecoratedWith` annotation.
|
||
|
||
.Applying a decorator
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
@DecoratedWith(PersonMapperDecorator.class)
|
||
public interface PersonMapper {
|
||
|
||
PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
|
||
|
||
PersonDto personToPersonDto(Person person);
|
||
|
||
AddressDto addressToAddressDto(Address address);
|
||
}
|
||
----
|
||
====
|
||
|
||
The decorator must be a sub-type of the decorated mapper type. You can make it an abstract class which allows to only implement those methods of the mapper interface which you want to customize. For all non-implemented methods, a simple delegation to the original mapper will be generated using the default generation routine.
|
||
|
||
The `PersonMapperDecorator` shown below customizes the `personToPersonDto()`. It sets an additional attribute which is not present in the source type of the mapping. The `addressToAddressDto()` method is not customized.
|
||
|
||
.Implementing a decorator
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
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;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
The example shows how you can optionally inject a delegate with the generated default implementation and use this delegate in your customized decorator methods.
|
||
|
||
For a mapper with `componentModel = "default"`, define a constructor with a single parameter which accepts the type of the decorated mapper.
|
||
|
||
When working with the component models `spring` or `jsr330`, this needs to be handled differently.
|
||
|
||
[[decorators-with-spring]]
|
||
==== Decorators with the Spring component model
|
||
|
||
When using `@DecoratedWith` on a mapper with component model `spring`, the generated implementation of the original mapper is annotated with the Spring annotation `@Qualifier("delegate")`. To autowire that bean in your decorator, add that qualifier annotation as well:
|
||
|
||
.Spring-based decorator
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
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;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
The generated class that extends the decorator is annotated with Spring's `@Primary` annotation. To autowire the decorated mapper in the application, nothing special needs to be done:
|
||
|
||
.Using a decorated mapper
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Autowired
|
||
private PersonMapper personMapper; // injects the decorator, with the injected original mapper
|
||
----
|
||
====
|
||
|
||
[[decorators-with-jsr-330]]
|
||
==== Decorators with the JSR 330 component model
|
||
|
||
JSR 330 doesn't specify qualifiers and only allows to specifically name the beans. Hence, the generated implementation of the original mapper is annotated with `@Named("fully-qualified-name-of-generated-implementation")` (please note that when using a decorator, the class name of the mapper implementation ends with an underscore). To inject that bean in your decorator, add the same annotation to the delegate field (e.g. by copy/pasting it from the generated class):
|
||
|
||
.JSR 330 based decorator
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public abstract class PersonMapperDecorator implements PersonMapper {
|
||
|
||
@Inject
|
||
@Named("org.examples.PersonMapperImpl_")
|
||
private PersonMapper delegate;
|
||
|
||
@Override
|
||
public PersonDto personToPersonDto(Person person) {
|
||
PersonDto dto = delegate.personToPersonDto( person );
|
||
dto.setName( person.getFirstName() + " " + person.getLastName() );
|
||
|
||
return dto;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
Unlike with the other component models, the usage site must be aware if a mapper is decorated or not, as for decorated mappers, the parameterless `@Named` annotation must be added to select the decorator to be injected:
|
||
|
||
.Using a decorated mapper with JSR 330
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Inject
|
||
@Named
|
||
private PersonMapper personMapper; // injects the decorator, with the injected original mapper
|
||
----
|
||
====
|
||
|
||
[WARNING]
|
||
====
|
||
`@DecoratedWith` in combination with component model `jsr330` is considered experimental as of the 1.0.0.CR2 release. The way the original mapper is referenced in the decorator or the way the decorated mapper is injected in the application code might still change.
|
||
====
|
||
|
||
[[customizing-mappings-with-before-and-after]]
|
||
=== Mapping customization with before-mapping and after-mapping methods
|
||
|
||
Decorators may not always fit the needs when it comes to customizing mappers. For example, if you need to perform the customization not only for a few selected methods, but for all methods that map specific super-types: in that case, you can use *callback methods* that are invoked before the mapping starts or after the mapping finished.
|
||
|
||
Callback methods can be implemented in the abstract mapper itself, in a type reference in `Mapper#uses`, or in a type used as `@Context` parameter.
|
||
|
||
.Mapper with @BeforeMapping and @AfterMapping hooks
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public abstract class VehicleMapper {
|
||
|
||
@BeforeMapping
|
||
protected void flushEntity(AbstractVehicle vehicle) {
|
||
// I would call my entity manager's flush() method here to make sure my entity
|
||
// is populated with the right @Version before I let it map into the DTO
|
||
}
|
||
|
||
@AfterMapping
|
||
protected void fillTank(AbstractVehicle vehicle, @MappingTarget AbstractVehicleDto result) {
|
||
result.fuelUp( new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType() ) );
|
||
}
|
||
|
||
public abstract CarDto toCarDto(Car car);
|
||
}
|
||
|
||
// Generates something like this:
|
||
public class VehicleMapperImpl extends VehicleMapper {
|
||
|
||
public CarDto toCarDto(Car car) {
|
||
flushEntity( car );
|
||
|
||
if ( car == null ) {
|
||
return null;
|
||
}
|
||
|
||
CarDto carDto = new CarDto();
|
||
// attributes mapping ...
|
||
|
||
fillTank( car, carDto );
|
||
|
||
return carDto;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
If the `@BeforeMapping` / `@AfterMapping` method has parameters, the method invocation is only generated if the return type of the method (if non-`void`) is assignable to the return type of the mapping method and all parameters can be *assigned* by the source or target parameters of the mapping method:
|
||
|
||
* A parameter annotated with `@MappingTarget` is populated with the target instance of the mapping.
|
||
* A parameter annotated with `@TargetType` is populated with the target type of the mapping.
|
||
* Parameters annotated with `@Context` are populated with the context parameters of the mapping method.
|
||
* Any other parameter is populated with a source parameter of the mapping.
|
||
|
||
For non-`void` methods, the return value of the method invocation is returned as the result of the mapping method if it is not `null`.
|
||
|
||
As with mapping methods, it is possible to specify type parameters for before/after-mapping methods.
|
||
|
||
.Mapper with @AfterMapping hook that returns a non-null value
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public abstract class VehicleMapper {
|
||
|
||
@PersistenceContext
|
||
private EntityManager entityManager;
|
||
|
||
@AfterMapping
|
||
protected <T> T attachEntity(@MappingTarget T entity) {
|
||
return entityManager.merge(entity);
|
||
}
|
||
|
||
public abstract CarDto toCarDto(Car car);
|
||
}
|
||
|
||
// Generates something like this:
|
||
public class VehicleMapperImpl extends VehicleMapper {
|
||
|
||
public CarDto toCarDto(Car car) {
|
||
if ( car == null ) {
|
||
return null;
|
||
}
|
||
|
||
CarDto carDto = new CarDto();
|
||
// attributes mapping ...
|
||
|
||
CarDto target = attachEntity( carDto );
|
||
if ( target != null ) {
|
||
return target;
|
||
}
|
||
|
||
return carDto;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
All before/after-mapping methods that *can* be applied to a mapping method *will* be used. <<selection-based-on-qualifiers>> can be used to further control which methods may be chosen and which not. For that, the qualifier annotation needs to be applied to the before/after-method and referenced in `BeanMapping#qualifiedBy` or `IterableMapping#qualifiedBy`.
|
||
|
||
The order of the method invocation is determined primarily by their variant:
|
||
|
||
1. `@BeforeMapping` methods without an `@MappingTarget` parameter are called before any null-checks on source
|
||
parameters and constructing a new target bean.
|
||
2. `@BeforeMapping` methods with an `@MappingTarget` parameter are called after constructing a new target bean.
|
||
3. `@AfterMapping` methods are called at the end of the mapping method before the last `return` statement.
|
||
|
||
Within those groups, the method invocations are ordered by their location of definition:
|
||
|
||
1. Methods declared on `@Context` parameters, ordered by the parameter order.
|
||
2. Methods implemented in the mapper itself.
|
||
3. Methods from types referenced in `Mapper#uses()`, in the order of the type declaration in the annotation.
|
||
4. Methods declared in one type are used after methods declared in their super-type.
|
||
|
||
*Important:* the order of methods declared within one type can not be guaranteed, as it depends on the compiler and the processing environment implementation.
|
||
|
||
|
||
[[using-spi]]
|
||
== Using the MapStruct SPI
|
||
=== Custom Accessor Naming Strategy
|
||
|
||
MapStruct offers the possibility to override the `AccessorNamingStrategy` via the Service Provide Interface (SPI). A nice example is the use of the fluent API on the source object `GolfPlayer` and `GolfPlayerDto` below.
|
||
|
||
.Source object GolfPlayer with fluent API.
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public class GolfPlayer {
|
||
|
||
private double handicap;
|
||
private String name;
|
||
|
||
public double handicap() {
|
||
return handicap;
|
||
}
|
||
|
||
public GolfPlayer withHandicap(double handicap) {
|
||
this.handicap = handicap;
|
||
return this;
|
||
}
|
||
|
||
public String name() {
|
||
return name;
|
||
}
|
||
|
||
public GolfPlayer withName(String name) {
|
||
this.name = name;
|
||
return this;
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
.Source object GolfPlayerDto with fluent API.
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
public class GolfPlayerDto {
|
||
|
||
private double handicap;
|
||
private String name;
|
||
|
||
public double handicap() {
|
||
return handicap;
|
||
}
|
||
|
||
public GolfPlayerDto withHandicap(double handicap) {
|
||
this.handicap = handicap;
|
||
return this;
|
||
}
|
||
|
||
public String name() {
|
||
return name;
|
||
}
|
||
|
||
public GolfPlayerDto withName(String name) {
|
||
this.name = name;
|
||
return this
|
||
}
|
||
}
|
||
----
|
||
====
|
||
|
||
We want `GolfPlayer` to be mapped to a target object `GolfPlayerDto` similar like we 'always' do this:
|
||
|
||
.Source object with fluent API.
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
@Mapper
|
||
public interface GolfPlayerMapper {
|
||
|
||
GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class );
|
||
|
||
GolfPlayerDto toDto(GolfPlayer player);
|
||
|
||
GolfPlayer toPlayer(GolfPlayerDto player);
|
||
|
||
}
|
||
----
|
||
====
|
||
|
||
This can be achieved with implementing the SPI `org.mapstruct.ap.spi.AccessorNamingStrategy` as in the following example. Here's an implemented `org.mapstruct.ap.spi.AccessorNamingStrategy`:
|
||
|
||
.CustomAccessorNamingStrategy
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
/**
|
||
* A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the
|
||
* form of {@code withProperty(value)}.
|
||
*/
|
||
public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
|
||
|
||
@Override
|
||
public boolean isGetterMethod(ExecutableElement method) {
|
||
String methodName = method.getSimpleName().toString();
|
||
return !methodName.startsWith( "with" ) && method.getReturnType().getKind() != TypeKind.VOID;
|
||
}
|
||
|
||
@Override
|
||
public boolean isSetterMethod(ExecutableElement method) {
|
||
String methodName = method.getSimpleName().toString();
|
||
return methodName.startsWith( "with" ) && methodName.length() > 4;
|
||
}
|
||
|
||
@Override
|
||
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
|
||
String methodName = getterOrSetterMethod.getSimpleName().toString();
|
||
return IntrospectorUtils.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName );
|
||
}
|
||
}
|
||
----
|
||
====
|
||
The `CustomAccessorNamingStrategy` makes use of the `DefaultAccessorNamingStrategy` (also available in mapstruct-processor) and relies on that class to leave most of the default behaviour unchanged.
|
||
|
||
To use a custom SPI implementation, it must be located in a separate JAR file together with the file `META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy` with the fully qualified name of your custom implementation as content (e.g. `org.mapstruct.example.CustomAccessorNamingStrategy`). This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar).
|
||
|
||
[TIP]
|
||
Fore more details: The example above is present in our examples repository (https://github.com/mapstruct/mapstruct-examples).
|
||
|
||
[mapping-exclusion-provider]
|
||
=== Mapping Exclusion Provider
|
||
|
||
MapStruct offers the possibility to override the `MappingExclusionProvider` via the Service Provider Interface (SPI).
|
||
A nice example is to not allow MapStruct to create an automatic sub-mapping for a certain type,
|
||
i.e. MapStruct will not try to generate an automatic sub-mapping method for an excluded type.
|
||
|
||
[NOTE]
|
||
====
|
||
The `DefaultMappingExclusionProvider` will exclude all types under the `java` or `javax` packages.
|
||
This means that MapStruct will not try to generate an automatic sub-mapping method between some custom type and some type declared in the Java class library.
|
||
====
|
||
|
||
.Source object
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
include::{processor-ap-test}/nestedbeans/exclusions/custom/Source.java[tag=documentation]
|
||
----
|
||
====
|
||
|
||
.Target object
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
include::{processor-ap-test}/nestedbeans/exclusions/custom/Target.java[tag=documentation]
|
||
----
|
||
====
|
||
|
||
.Mapper definition
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
include::{processor-ap-test}/nestedbeans/exclusions/custom/ErroneousCustomExclusionMapper.java[tag=documentation]
|
||
----
|
||
====
|
||
|
||
We want to exclude the `NestedTarget` from the automatic sub-mapping method generation.
|
||
|
||
.CustomMappingExclusionProvider
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
include::{processor-ap-test}/nestedbeans/exclusions/custom/CustomMappingExclusionProvider.java[tag=documentation]
|
||
----
|
||
====
|
||
|
||
To use a custom SPI implementation, it must be located in a separate JAR file
|
||
together with the file `META-INF/services/org.mapstruct.ap.spi.MappingExclusionProvider` with the fully qualified name of your custom implementation as content
|
||
(e.g. `org.mapstruct.example.CustomMappingExclusionProvider`).
|
||
This JAR file needs to be added to the annotation processor classpath
|
||
(i.e. add it next to the place where you added the mapstruct-processor jar).
|
||
|
||
|
||
[[custom-builder-provider]]
|
||
=== Custom Builder Provider
|
||
|
||
MapStruct offers the possibility to override the `DefaultProvider` via the Service Provider Interface (SPI).
|
||
A nice example is to provide support for a custom builder strategy.
|
||
|
||
.Custom Builder Provider which disables Builder support
|
||
====
|
||
[source, java, linenums]
|
||
[subs="verbatim,attributes"]
|
||
----
|
||
include::{processor-ap-main}/spi/NoOpBuilderProvider.java[tag=documentation]
|
||
----
|
||
====
|