MapStruct 1.4.2.最终参考指南

MapStruct 1.4.2.最终参考指南

MapStruct 1.4.2.Final Reference Guide

Gunnar Morling, Andreas Gudian, Sjaak Derksen, Filip Hrisafov and the MapStruct community2021-01-31

Translated by RiceMarch ricemarch@foxmail.com 由白云苍饭进行翻译

序言

这是MapStruct的参考文档,MapStruct是一个用于生成类型安全、高性能和无依赖的bean映射代码的注解处理器。本指南涵盖了MapStruct提供的所有功能。如果本指南没有回答你所有的问题,你可以加入MapStruct的Google群组来获得帮助。

如果您在本指南中发现了任何错误或者用词不当,请在 MapStruct GitHub 仓库中打开一个问题与我们取得联系,或者通过发送一个拉取请求来修复它。

本文采用Creative Commons Attribution-ShareAlike 4.0 International License.进行许可。

1. 介绍

MapStruct 是一款用于用于生成类型安全的Bean映射类的注解器。

您所要做的就是定义一个映射器接口,在该接口上声明您所需的所有映射方法。通过编译,MapStruct将会生成该接口的实现。该接口的实现使用朴素的Java方法来实现源对象和目标对象之间的映射,即没有反射等情况的存在

与手写映射代码相比,MapStruct可以节省时间,而手写映射代码则过于繁琐且容易出错。通过遵循约定俗成的配置方式,MapStruct使用默认值进行大部分映射,但在配置或实现特殊行为时,MapStruct同样可以实现自由编写。

与动态映射框架相比,MapStruct具有以下优点:

  • 通过使用普通方法调用代替反射来快速执行。

  • 编译时类型安全:只有指明的对象和属性可以相互映射,不会出现一个order entity 被映射为customer DTO 等情况。

  • 在build时即可清除映射错误:

    • 映射不完整(并非所有的目标属性都被映射)
    • 映射不正确(找不到合适的映射方法或类型转换)

2. 使用

MapStruct是一个基于JSR 269的Java注释处理器,因此可以在命令行构建(javac、Ant、Maven等)以及IDE中使用。

它包含以下Artifacts:

  • org.mapstruct:mapstruct: 包含必需的注解,例如@Mapping
  • org.mapstruct:mapstruct-processor: 包含生成映射器实现的注解处理器

2.1 Apache Maven

对于基于Maven的项目,请在你的POM文件中添加以下内容

Example 1. Maven configuration

...
<properties>
    <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
...

Tips:

If you are working with the Eclipse IDE, make sure to have a current version of the M2E plug-in. When importing a Maven project configured as shown above, it will set up the MapStruct annotation processor so it runs right in the IDE, whenever you save a mapper type. Neat, isn’t it?

To double check that everything is working as expected, go to your project’s properties and select “Java Compiler” → “Annotation Processing” → “Factory Path”. The MapStruct processor JAR should be listed and enabled there. Any processor options configured via the compiler plug-in (see below) should be listed under “Java Compiler” → “Annotation Processing”.

If the processor is not kicking in, check that the configuration of annotation processors through M2E is enabled. To do so, go to “Preferences” → “Maven” → “Annotation Processing” and select “Automatically configure JDT APT”. Alternatively, specify the following in the properties section of your POM file: <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.

2.2. Gradle

添加以下内容到你的Gradle构建文件中

Example 2. Gradle configuration

...
plugins {
    ...
    id "com.diffplug.eclipse.apt" version "3.26.0" // Only for Eclipse
}

dependencies {
    ...
    implementation "org.mapstruct:mapstruct:${mapstructVersion}"
    annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"

    // If you are using mapstruct in test code
    testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
}
...

你可以在GitHub上的mapstruct-examples项目中找到一个完整的例子。

2.3. Apache Ant

在您的 build.xml 文件中添加配置如下的 javac 任务,以便在您的基于 Ant 的项目中启用 MapStruct。根据项目布局的需要调整路径。

Example 3. Ant configuration

...
<javac
    srcdir="src/main/java"
    destdir="target/classes"
    classpath="path/to/mapstruct-1.4.2.Final.jar">
    <compilerarg line="-processorpath path/to/mapstruct-processor-1.4.2.Final.jar"/>
    <compilerarg line="-s target/generated-sources"/>
</javac>
...

你可以在GitHub上的mapstruct-examples项目中找到一个完整的例子。

2.4 选项配置

MapStruct代码生成器可以使用注释选项进行配置。

当直接通过javac调用,这些选项以-Akey=value的形式传递给编译器(compiler)。当通过Maven使用时,任何处理器选项都可以通过Maven处理器插件配置中的options元素来传递,如:

Example 4. Maven configuration

...
<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>
        <!-- due to problem in maven-compiler-plugin, for verbose mode add showWarnings -->
        <showWarnings>true</showWarnings>
        <compilerArgs>
            <arg>
                -Amapstruct.suppressGeneratorTimestamp=true
            </arg>
            <arg>
                -Amapstruct.suppressGeneratorVersionInfoComment=true
            </arg>
            <arg>
                -Amapstruct.verbose=true
            </arg>
        </compilerArgs>
    </configuration>
</plugin>
...

Example 5. Gradle configuration

...
compileJava {
    options.compilerArgs += [
        '-Amapstruct.suppressGeneratorTimestamp=true',
        '-Amapstruct.suppressGeneratorVersionInfoComment=true',
        '-Amapstruct.verbose=true'
    ]
}
...

有以下选择:

The following options exist:

Table 1. MapStruct processor options

选项/Option作用/Purpose 默认值/Default

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.verbose

If set to true, MapStruct in which MapStruct logs its major decisions. Note, at the moment of writing in Maven, also showWarnings needs to be added due to a problem in the maven-compiler-plugin configuration.

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 a 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.defaultInjectionStrategy

The type of the injection in mapper via parameter uses. This is only used on annotated based component models such as CDI, Spring and JSR 330.

Supported values are:

  • field: dependencies will be injected in fields

  • constructor: will be generated constructor. Dependencies will be injected via constructor.

When CDI componentModel a default constructor will also be generated. If a injection strategy is given for a specific mapper via @Mapper#injectionStrategy(), the value from the annotation takes precedence over the option.

field

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

2.5. 在Java 9中使用 MapStruct

MapStruct实验性支持Java 9(JPMS)。

Java 9 带来的一个核心变化是JDK的模块化。其带来的影响是为了使用javax.annotation.Generated annotation,需要为一个项目启用特定的模块。@Generated 已被Mapstruct添加在生成映射类中,用于将其标记为生成的代码,说明生成日期、生成器版本等。

为了使用@Generated注解,必须启用java.xml.ws.annotation模块。使用Maven时,可以这样做。

export MAVEN_OPTS="--add-modules java.xml.ws.annotation"

如果@Generated注解不生效,MapStruct会检测到这种情况,不会将其添加到生成的映射器中。

In Java 9 java.annotation.processing.Generated was added (part of the java.compiler module), if this annotation is available then it will be used.

3. 定义一个mapper映射

在本节中,您将了解如何使用MapStruct定义bean mapper,以及有哪些选项可以进行操作。

Example 6. Java interface to define a mapper

@Mapper
public interface CarMapper {

    @Mapping(source = "make", target = "manufacturer")
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car);

    @Mapping(source = "name", target = "fullName")
    PersonDto personToPersonDto(Person person);
}

通过 @Mapper 注解, MapStruct代码生成器在代码build时创建CarMapper 接口的实现。

在生成的方法实现中,源类型(如Car)的所有可读属性将被复制到目标类型(如CarDto)的相应属性中。

  • 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。
  • 当一个属性在目标实体中拥有不同的名称时,可以通过@Mapping注解来指定。

Tips.1

The property name as defined in the JavaBeans specification must be specified in the @Mapping annotation, e.g. seatCount for a property with the accessor methods getSeatCount() and setSeatCount().

Tips.2

By means of the @BeanMapping(ignoreByDefault = true) the default behavior will be explicit mapping, meaning that all mappings have to be specified by means of the @Mapping and no warnings will be issued on missing target properties.

Tips.3

Fluent setters are also supported. Fluent setters are setters that return the same type as the type being modified.

E.g.

public Builder seatCount(int seatCount) {
    this.seatCount = seatCount;
    return this;
}

为了更好的理解MapStruct的功能,我们来看由MapStruct生成的carToCarDto()的具体实现

Example 7. Code generated by MapStruct

// GENERATED CODE
public class CarMapperImpl implements CarMapper {

    @Override
    public CarDto carToCarDto(Car car) {
        if ( car == null ) {
            return null;
        }

        CarDto carDto = new CarDto();

        if ( car.getFeatures() != null ) {
            carDto.setFeatures( new ArrayList<String>( car.getFeatures() ) );
        }
        carDto.setManufacturer( car.getMake() );
        carDto.setSeatCount( car.getNumberOfSeats() );
        carDto.setDriver( personToPersonDto( car.getDriver() ) );
        carDto.setPrice( String.valueOf( car.getPrice() ) );
        if ( car.getCategory() != null ) {
            carDto.setCategory( car.getCategory().toString() );
        }
        carDto.setEngine( engineToEngineDto( car.getEngine() ) );

        return carDto;
    }

    @Override
    public PersonDto personToPersonDto(Person person) {
        //...
    }

    private EngineDto engineToEngineDto(Engine engine) {
        if ( engine == null ) {
            return null;
        }

        EngineDto engineDto = new EngineDto();

        engineDto.setHorsePower(engine.getHorsePower());
        engineDto.setFuel(engine.getFuel());

        return engineDto;
    }
}

MapStruct 的生成理念是生成尽可能多的像你自己亲手写的的代码,这正意味着值是通过普通的getter/setter调用而不是反射或其他类似的方式从源复制到目标。

如示例所示,生成的代码会考虑到任何通过@Mapping指定的名称映射。如果映射属性的类型在源实体和目标实体中是不同的,MapStruct 将通过自动转换(例如,对于价格属性,请参见隐式类型转换)或选择调用/创建另一个映射方法(例如,对于驱动程序/引擎属性,请参见 映射对象引用)。

3.2 映射组成(实验性内容)

MapStruct支持使用元注解,现在@Mapping注解支持ElementType#METHOD以及使用ElementType#ANNOTATION_TYPE@Target。这使得@Mapping可以用于其他(用户定义的)注解,以达到重复使用的目的。

@Retention(RetentionPolicy.CLASS)
@Mapping(target = "id", ignore = true)
@Mapping(target = "creationDate", expression = "java(new java.util.Date())")
@Mapping(target = "name", source = "groupName")
public @interface ToEntity { }

此举可以在不需要有一个共同的基础类型的情况下描述一个Entity。举个栗子,在下面的StorageMapper中,ShelveEntityBoxEntity并不共享一个共同的基础类型。

@Mapper
public interface StorageMapper {

    StorageMapper INSTANCE = Mappers.getMapper( StorageMapper.class );

    @ToEntity
    @Mapping( target = "weightLimit", source = "maxWeight")
    ShelveEntity map(ShelveDto source);

    @ToEntity
    @Mapping( target = "label", source = "designation")
    BoxEntity map(BoxDto source);
}

然而,他们有一些相同的属性,@ToEntity注解假设这些目标Beans ShelveEntityBoxEntity 有以下属性: "id", "creationDate""name"》此外,它还假设源Bean ShelveDtoBoxDto总是有一个属性 "groupName"。这个概念也被称为 “duck-typing”。换句话说,如果它的叫声像鸭子,走路像鸭子,它可能是一只鸭子。只关心行为,不关心类型

这个功能还是实验性的。错误信息还不成熟:显示发生问题的方法,以及@Mapping注解中的相关值。然而,组成方面是不可见的。这些信息 “就像”@Mapping会直接出现在相关方法上一样。因此,用户应该谨慎使用这个功能,特别是当不确定某个属性是否始终存在时。

一个更安全(但也更啰嗦)的方法是在目标Bean和源Bean上定义基类/接口,然后使用@InheritConfiguration来实现同样的结果。(有关 映射配置继承,Mapping configuration inheritance)

3.3 在Mapper中添加自定义方法

在一些场景中,可能会需要手动实现一个从一种类型到另一种类型的特定的mapping,这种映射不能由MapStruct自动生成。 处理这个问题的一种方法是在另一个类上实现自定义方法,然后由MapStruct生成的映射器使用。

另外,当使用Java 8或更高版本时,你可以直接在mapper接口中实现自定义方法作为默认方法。如果参数和返回类型匹配,生成的代码将调用默认方法。

@Mapper
public interface CarMapper {

    @Mapping(...)
    ...
    CarDto carToCarDto(Car car);

    default PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}

MapStruct生成的类实现了carToCarDto()方法。carToCarDto()中生成的代码将在映射driver属性时调用手动实现的personToPersonDto()方法。

mapper的定义可以不使用interface而使用抽象类的形式,并直接在映射器类中实现自定义方法。在这种情况下,MapStruct将会生成包含所有抽象方法的实现的抽象类的扩展。与声明默认方法相比,这种方法的一个优点是可以在mapper类中声明额外的字段。

之前的例子中,从Person到PersonDto的映射需要一些特殊的逻辑,那么可以这样定义。

Example 9. Mapper defined by an abstract class

@Mapper
public abstract class CarMapper {

    @Mapping(...)
    ...
    public abstract CarDto carToCarDto(Car car);

    public PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}

MapStruct将会生成一个包含抽象方法carToCarDto()实现的抽象类CarMapper的子类。

carToCarDto()中生成的代码在映射driver属性时,会调用手动实现的personToPersonDto()方法。

3.4 具有多源参数的Mapper方法

MapStruct还支持带有多个源参数的映射方法。这是一种很给力的方法,例如,为了将几个实体(entities)合并到一个数据传输对象(data transfer object)中。下面是一个例子。

Example 10. Mapping method with several source parameters

@Mapper
public interface AddressMapper {

    @Mapping(source = "person.description", target = "description")
    @Mapping(source = "address.houseNo", target = "houseNumber")
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}

上午所示的mapping方法需要两个源参数,并返回一个组合的目标对象。与单参数mapping方法一样,属性是通过名称进行映射的。

如果多个源对象定义了具有相同名称的属性,则必须使用 @Mapping 注解指定用于检索属性的源参数,如示例中描述属性所示。如果这样的歧义没有被解决则将会产生error,对于在给定的source object中只存在一次的属性,可以不指定source object的名称,因为它可以自动确定。

Tips.1

Specifying the parameter in which the property resides is mandatory when using the @Mapping annotation.

Tips.2

Mapping methods with several source parameters will return null in case all the source parameters are null. Otherwise the target object will be instantiated and all properties from the provided parameters will be propagated.

MapStruct还提供了直接引用源参数的方式。

Example 11. Mapping method directly referring to a source parameter

@Mapper
public interface AddressMapper {

    @Mapping(source = "person.description", target = "description")
    @Mapping(source = "hn", target = "houseNumber")
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}

在这种情况下,源参数直接被映射到目标中,如上例所示。参数hn,一个非bean类型(在本例中是java.lang.Integer)被映射到houseNumber

3.5 映射嵌套bean属性到当前Target

如果你不想显式命名嵌套的源Bean的所有属性,你可以使用.作为目标。这将告诉MapStruct将源Bean中的所有属性映射到目标对象。下面是一个例子。

Example 12. use of “target this” annotation “.”

@Mapper
 public interface CustomerMapper {

     @Mapping( target = "name", source = "record.name" )
     @Mapping( target = ".", source = "record" )
     @Mapping( target = ".", source = "account" )
     Customer customerDtoToCustomer(CustomerDto customerDto);
 }

生成的代码会将CustomerDto.record中的每一个属性直接映射到Customer中,而不需要手动命名其中的任何一个。Customer.account也是如此。

当嵌套映射有冲突时,可以通过明确定义映射来解决。例如在上面的例子中。name同时出现在CustomerDto.recordCustomerDto.account中。映射@Mapping( target = "name", source = "record.name")可以解决这个冲突。

这种 "target this "的标记方法在将分层对象映射到平面对象时非常有用,反之亦然(@InheritInverseConfiguration)。

3.6 更新现有的bean实例

在某些情况下,您需要的映射不是创建一个新的目标类型实例,而是更新该类型的现有实例。这种映射可以通过为目标对象添加一个参数,并用@MappingTarget标记这个参数来实现。下面是一个例子。

Example 13. Update method

@Mapper
public interface CarMapper {

   void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}

updateCarFromDto()方法生成的代码将用给定CarDto对象的属性更新传递的Car实例。标记为映射目标的参数可能只有一个。你也可以将该方法的返回类型设置为目标参数的类型,而不是void,在此情况下会生成的实现更新传递的映射目标并将其也返回。它同样允许流式调用mapping方法(fluent invocations of mapping methods.)

对于CollectionMappingStrategy.ACCESSOR_ONLY目标bean的集合或Map类型的属性将被清除,然后用相应的源集合或地图的值填充。除此以外,对于CollectionMappingStrategy.ADDER_PREERRFEDCollectionMappingStrategy.TARGET_IMMUTABLE目标将不会被清除,而会立即填充值。

3.7 直接使用字段访问进行映射

MapStruct 同样支持映射没有getter/setter 方法的public字段。MapStruct如果找不到合适的属性的getter/setter方法,就会把字段作为读/写访问器。

如果一个字段是publicpublic final,则被视为读访问器。如果一个字段是static 的,则不被视为读存取器。

只有当一个字段是public时,它才被认为是一个写访问器。如果一个字段是 final和/或static,则不被视为写访问器。

简单的例子:

Example 14. Example classes for mapping

public class Customer {

   private Long id;
   private String name;

   //getters and setter omitted for brevity
}

public class CustomerDto {

   public Long id;
   public String customerName;
}

@Mapper
public interface CustomerMapper {

   CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );

   @Mapping(source = "customerName", target = "name")
   Customer toCustomer(CustomerDto customerDto);

   @InheritInverseConfiguration
   CustomerDto fromCustomer(Customer customer);
}

对于上面的配置,生成的mapper 如下:

Example 15. Generated mapper for example classes

// GENERATED CODE
public class CustomerMapperImpl implements CustomerMapper {

   @Override
   public Customer toCustomer(CustomerDto customerDto) {
       // ...
       customer.setId( customerDto.id );
       customer.setName( customerDto.customerName );
       // ...
   }

   @Override
   public CustomerDto fromCustomer(Customer customer) {
       // ...
       customerDto.id = customer.getId();
       customerDto.customerName = customer.getName();
       // ...
   }
}

你可以找到更完整的项目例子在 mapstruct-examples-field-mapping Github网站中

3.8 使用 Builders

MapStruct 也支持通过builders映射不可变类型,当执行映射时,MapStruct会检查是否有被映射类型的builder。这是通过 BuilderProvider SPI完成的。如果某个类型存在Builder,那么该Builder将被用于映射。

BuilderProvider的默认实现假设如下:

  • 该类型有一个无参数的公共静态构建器创建方法,返回一个构建器。所以例如Person有一个public static 方法,返回PersonBuilder
  • 构建器类型有一个无参数的public方法(build方法),它返回被构建的类型。在我们的例子中,PersonBuilder有一个返回Person的方法。
  • 在有多个构建方法的情况下,MapStruct会寻找一个叫做build的方法,如果存在这样的方法,那么就会使用这个方法,否则就会产生一个编译错误。
  • 可以通过使用@Builder@BeanMapping@Mapper@MapperConfig中定义特定的build方法。
  • 如果有多个builder创建方法满足上述条件,那么DefaultBuilderProvider SPI将抛出MoreThanOneBuilderCreationMethodException。如果出现MoreThanOneBuilderCreationMethodException,MapStruct将在编译中写一个警告,并且不使用任何构建器。

如果找到了这样的类型,那么MapStruct将使用该类型来执行映射到(即它将寻找进入该类型的setter)。为了完成映射,MapStruct会生成代码,调用构建器的构建方法。

Tips.1

Builder detection can be switched off by means of @Builder#disableBuilder. MapStruct will fall back on regular getters / setters in case builders are disabled.

Tips.2

The Object factories are also considered for the builder type. E.g. If an object factory exists for our PersonBuilder then this factory would be used instead of the builder creation method.

Example 16. Person with Builder example

public class Person {

    private final String name;

    protected Person(Person.Builder builder) {
        this.name = builder.name;
    }

    public static Person.Builder builder() {
        return new Person.Builder();
    }

    public static class Builder {

        private String name;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Person create() {
            return new Person( this );
        }
    }
}

Example 17. Person Mapper definition

public interface PersonMapper {

    Person map(PersonDto dto);
}

Example 18. Generated mapper with builder

// GENERATED CODE
public class PersonMapperImpl implements PersonMapper {

    public Person map(PersonDto dto) {
        if (dto == null) {
            return null;
        }

        Person.Builder builder = Person.builder();

        builder.name( dto.getName() );

        return builder.create();
    }
}

支持生成器框架:

  • Lombok - 需要将Lombok类放在一个单独的模块中。更多信息请查看rzwitserloot/lombok#1538 要设置Lombok与MapStruct,请参考 Lombok
  • AutoValue
  • Immutables - 当Immutables存在于注解处理器路径上时,将默认使用ImmutablesAccessorNamingStrategyImmutablesBuilderProvider
  • FreeBuilder - 当注解处理器路径上存在FreeBuilder时,那么将默认使用FreeBuilderAccessorNamingStrategy。当使用FreeBuilder时,那么应该遵循JavaBean惯例,否则MapStruct将无法识别流畅的获取器。
  • 如果实现支持默认BuilderProvider的定义规则,那么对于自定义的构建器(手写的)也是有效的。否则,你需要写一个自定义的BuilderProvider

Tips

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.

3.9 使用Constructors

MapStruct支持使用constructors生成目标类型。在进行映射时,MapStruct 会检查是否有被映射类型的构建器。如果没有构建器,那么MapStruct会寻找一个可访问的构造函数。当有多个构造函数时,则按以下方法选择应该使用的构造函数。

  • 如果一个构造函数被注解了名为 @Default (来自任何包,请看 Non-shipped annotations) 那么它将被使用。
  • 如果存在一个单一的公共构造函数,那么它将被用于构造对象,而其他非公共构造函数将被忽略。
  • 如果存在一个无参数的构造函数,那么它将被用于构造对象,而其他构造函数将被忽略
  • I如果有多个符合条件的构造函数,那么就会因为构造函数含糊不清而出现编译错误。为了避免这种歧义,可以使用 @Default注解 (来自任何包,参见 Non-shipped annotations注解)

Example 19. Deciding which constructor to use

public class Vehicle {

    protected Vehicle() { }

    // MapStruct will use this constructor, because it is a single public constructor
    public Vehicle(String color) { }
}

public class Car {

    // MapStruct will use this constructor, because it is a parameterless empty constructor
    public Car() { }

    public Car(String make, String color) { }
}

public class Truck {

    public Truck() { }

    // MapStruct will use this constructor, because it is annotated with @Default
    @Default
    public Truck(String make, String color) { }
}

public class Van {

    // There will be a compilation error when using this class because MapStruct cannot pick a constructor

    public Van(String make) { }

    public Van(String make, String color) { }

}

当使用构造函数时,那么将使用构造函数的参数名称并与目标属性相匹配。当构造函数有一个名为@ConstructorProperties的注解时(来自任何包,请看 Non-shipped annotations),那么这个注解将被用来获取参数的名称。

Tips

When an object factory method or a method annotated with @ObjectFactory exists, it will take precedence over any constructor defined in the target. The target object constructor will not be used in that case.

Example 20. Person with constructor parameters

public class Person {

    private final String name;
    private final String surname;

    public Person(String name, String surname) {
        this.name = name;
        this.surname = surname;
    }
}

Example 21. Person With Constructor Mapper definition

public interface PersonMapper {

    Person map(PersonDto dto);
}

Example 22. Generated mapper with constructor

// GENERATED CODE
public class PersonMapperImpl implements PersonMapper {

    public Person map(PersonDto dto) {
        if (dto == null) {
            return null;
        }

        String name;
        String surname;
        name = dto.getName();
        surname = dto.getSurname();

        Person person = new Person( name, surname );

        return person;
    }
}

4. 获取一个mapper

4.1 Mappers factory(没有依赖注入)

当不使用DI框架时,mappr实例可以通过org.mapstruct.factory.Mappers类获取。仅需调用getMapper()方法,传递要返回的mapper的接口类型。

Example 23. Using the Mappers factory

CarMapper mapper = Mappers.getMapper( CarMapper.class );

按照惯例,mapper接口应该定义一个名为INSTANCE的成员,它持有mapper类型的一个实例。

Example 24. Declaring an instance of a mapper (interface)

@Mapper
public interface CarMapper {

    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );

    CarDto carToCarDto(Car car);
}

Example 25. Declaring an instance of a mapper (abstract class)

@Mapper
public abstract class CarMapper {

    public static final CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );

    CarDto carToCarDto(Car car);
}

这种模式使得使用者可以很有容易的使用mapper对象,而无需反复创建新的实例对象。

Example 26. Accessing a mapper

Car car = ...; 
CarDto dto = CarMapper.INSTANCE.carToCarDto( car );

值得注意的是,MapStruct生成的映射器是无状态和线程安全的,因此可以安全地从多个线程同时访问。

4.2 使用依赖注入

如果你使用诸如CDI (Contexts and Dependency Injection for JavaTM EE)或者 Spring Framework的依赖注入框架,那么更加建议通过依赖注入获取mapper对象而不是通过上文描述的Mappers类。您可以通过@Mapper#componentModel或使用配置选项中描述的处理器选项来指定生成的映射器类应基于的组件模型。

目前支持CDI和Spring(后者通过其自定义注释或使用JSR 330注释)。请参阅配置选项,了解componentModel属性的允许值,这些值与 mapstruct.defaultComponentModel处理器选项相同。在这两种情况下,所需的注解将被添加到生成的映射器实现类中,以便使其同样受到依赖注入的影响。下面展示了一个使用CDI的例子。

Example 27. A mapper using the CDI component model

@Mapper(componentModel = "cdi")
public interface CarMapper {

    CarDto carToCarDto(Car car);
}

生成的映射器实现将被标记为@ApplicationScoped注解,因此可以使用@Inject注解注入字段、构造参数等。

Example 28. Obtaining a mapper via dependency injection

@Inject
private CarMapper mapper;

使用其他映射器类的映射器(参见调用其他映射器)将使用配置的组件模型获得这些映射器。因此,如果前面例子中的CarMapper使用了另一个映射器,那么这个其他映射器也必须是一个可注入的CDI Bean。

4.3 注入策略

当使用依赖注入,时,你可以选择字段和构造函数注入。这可以通过@Mapper@MapperConfig注解提供注入策略来实现。

Example 29. Using constructor injection

@Mapper(componentModel = "cdi", uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CarMapper {
    CarDto carToCarDto(Car car);
}

生成的映射器将注入所有在uses属性中定义的类。当使用InjectionStrategy#CONSTRUCTOR时,构造函数会有相应的注释,而字段不会。当使用InjectionStrategy#FIELD时,注释在字段本身。目前,默认的注入策略是字段注入,但可以通过配置选项进行配置。建议使用构造函数注入,以简化测试。

Tips For abstract classes or decorators setter injection should be used.

5. 数据类型转换

映射的属性在源对象和目标对象中并非总是具有相同的类型。例如,一个属性在源Bean中可能是 int类型,但在目标Bean中可能是 Long类型。

另一个例子是对其他对象的引用,这些对象应被映射到目标模型中的相应类型例如,Car类可能有一个 Person类型的属性 driver,在映射 Car对象时,需要将其转换为 PersonDto对象。

在本节,你将学习到如何MapStruct是如何应对数据类型转换的。

5.1 隐式类型抓换

MapStruct在许多情况下会自动处理类型转换。例如,如果一个属性在源Bean中的类型是int,而在目标Bean中的类型是String,那么生成的代码将分别通过调用 String#valueOf(int)Integer#parseInt(String)来执行隐式转换。

目前,以下转换是自动应用的:

  • 在所有Java元数据类型以及其包装类间的转换,例如在intIntegerbooleanBoolean等之间。生成的代码具有 null感知运算,即当将封装类型转换为相应的基元类型时,将执行null检查。
  • 在所有Java基数类型和封装类型之间,例如在intlongbyteInteger之间。

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.

  • 在所有Java基元类型(包括它们的封装器)和String之间,例如intStringBooleanString之间。可以指定由java.text.DecimalFormat解析的格式字符串。

Example 30. Conversion from int to String

@Mapper
public interface CarMapper {

    @Mapping(source = "price", numberFormat = "$#.00")
    CarDto carToCarDto(Car car);

    @IterableMapping(numberFormat = "$#.00")
    List<String> prices(List<Integer> prices);
}
  • enum类型和String
  • 在大数类型(java.math.BigIntegerjava.math.BigDecimal)和Java元类型(包括它们的封装类)以及String之间。可以指定java.text.DecimalFormat所理解的格式字符串。

Example 31. Conversion from BigDecimal to String

@Mapper
public interface CarMapper {

    @Mapping(source = "power", numberFormat = "#.##E0")
    CarDto carToCarDto(Car car);

}
  • JAXBElement<T>T之间,List<JAXBElement<T>List<T>之间的转换。
  • java.util.Calendar/java.util.Date和JAXB的XMLGregorianCalendar之间的转换。
  • java.util.Date/XMLGregorianCalendarString之间的转换。可以通过dateFormat选项指定java.text.SimpleDateFormat所理解的格式字符串,如:

Example 32. Conversion from Date to String

@Mapper
public interface CarMapper {

    @Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
    CarDto carToCarDto(Car car);

    @IterableMapping(dateFormat = "dd.MM.yyyy")
    List<String> stringListToDateList(List<Date> dates);
}
  • 在Jodasorg.joda.time.DateTimeorg.joda.time.LocalDateTimeorg.joda.time.LocalDateorg.joda.time.LocalTimeString之间。java.text.SimpleDateFormat理解的格式字符串可以通过dateFormat选项指定(见上文)。
  • 在Jodasorg.joda.time.DateTimejavax.xml.datatype.XMLGregorianCalendarjava.util.Calendar之间。
  • 在Jodasorg.joda.time.LocalDateTimeorg.joda.time.LocalDatejavax.xml.datatype.XMLGregorianCalendarjava.util.Date之间。
  • java.time.LocalDatejava.time.LocalDateTimejavax.xml.datatype.XMLGregorianCalendar之间。
  • 介于java.time.ZonedDateTimejava.time.LocalDateTimejava.time.LocalDatejava.time.LocalTimejava.time.LocalTime从Java 8 Date-Time包和String之间。可以通过dateFormat选项指定java.text.SimpleDateFormat所理解的格式字符串(见上文)。
  • java.time.Instantjava.time.Durationjava.time.Period之间使用Java 8 Date-Time包中的parse方法从String映射到String,并使用toString映射到String
  • 在Java 8 Date-Time包中的java.time.ZonedDateTimejava.util.Date之间,当从给定的Date映射一个ZonedDateTime时,使用系统默认的时区。
  • 在Java 8 Date-Time包中的java.time.LocalDateTimejava.util.Date之间,使用UTC作为时区。
  • 在Java 8 Date-Time包中的java.time.LocalDatejava.util.Date/java.sql.Date之间,使用UTC作为时区。
  • 在Java 8 Dat-Time包中的java.time.Instantjava.util.Date之间。
  • 在Java 8 Dat-Time包中的java.time.ZonedDateTimejava.util.Calendar之间。
  • java.sql.Datejava.util.Date之间。
  • java.sql.Timejava.util.Date之间。
  • java.sql.Timestampjava.util.Date之间。
  • 当从 "字符串 "转换时,省略 Mapping#dateFormat,会导致使用默认的模式和日期格式符号来转换默认的本地环境。这个规则的一个例外是XmlGregorianCalendar,它的结果是根据XML Schema 1.0 Part 2, Section 3.2.7-14.1, Lexical Representation解析String
  • java.util.CurrencyString之间。
    • 当从String转换时,值需要是有效的ISO-4217字母代码,否则会抛出IllegalArgumentException

5.2 映射对象引用

通常情况下,一个对象不仅有基元属性,还可以引用其他对象。例如,Car类可以包含对 Person对象(代表汽车司机)的引用,该对象应映射到 CarDto类引用的 PersonDto对象。

在这种情况下,只要为被引用的对象类型也定义一个映射方法即可。

Example 33. Mapper with one mapping method using another

@Mapper
public interface CarMapper {

    CarDto carToCarDto(Car car);

    PersonDto personToPersonDto(Person person);
}

carToCarDto()方法生成的代码将调用personToPersonDto()方法对driver属性进行映射,而personToPersonDto()生成的实现则执行person对象的映射。

这样就可以映射任意的深层对象图。当从实体映射到数据传输对象时,通常需要在某一点上切断对其他实体的引用。要做到这一点,可以实现一个自定义的映射方法(见下一节),例如,将被引用的实体映射到目标对象中的id。

当生成映射方法的实现时,MapStruct将对源对象和目标对象中的每个属性对应用以下规则。

  1. 如果源属性和目标属性有相同的类型,则值会被直接进行简单拷贝。如果属性是一个集合(如List),则该集合的副本将被设置到目标属性中。
  2. 如果源属性和目标属性类型不一致,检查是否有另一个映射方法将源属性的类型作为参数类型,将目标属性的类型作为返回类型。
  3. 如果不存在这样的方法,MapStruct将查看是否存在属性的源和目标类型的内置转换。如果存在,生成的映射代码将应用这种转换。
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值