前言

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

您在本指南中发现了拼写错误或其他错误?请通过在MapStruct GitHub存储库中打开一个问题告诉我们,或者更好的是,帮助社区并发送拉取请求来修复它!

1.简介

MapStruct是一个Java 注释处理器,用于生成类型安全的bean映射类。

您所要做的就是定义一个mapper接口,该接口声明任何所需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用普通的Java方法调用来在源对象和目标对象之间进行映射,即没有反射或类似。

与手工编写映射代码相比,MapStruct通过生成繁琐且易于编写的代码来节省时间。遵循约定优于配置方法,MapStruct使用合理的默认值,但在配置或实现特殊行为时会采取措施。

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

  • 通过使用普通方法调用而不是反射来快速执行

  • 编译时类型安全:只能映射相互映射的对象和属性,不会将订单实体意外映射到客户DTO等。

  • 在构建时清除错误报告,如果

    • 映射不完整(并非所有目标属性都已映射)

    • 映射不正确(找不到合适的映射方法或类型转换)

2.设置

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

它包含以下工件:

  • org.mapstruct:mapstruct:包含所需的注释,例如@Mapping

  • org.mapstruct:mapstruct-processor:包含生成映射器实现的注释处理器

2.1。Apache Maven

对于基于Maven的项目,将以下内容添加到POM文件中以使用MapStruct:

示例1. Maven配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
<properties>
    <org.mapstruct.version>1.3.0.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.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>
...

如果您正在使用Eclipse IDE,请确保拥有当前版本的M2E插件导入如上所示配置的Maven项目时,它将设置MapStruct注释处理器,以便在您保存映射器类型时在IDE中运行。整洁,不是吗?

要仔细检查一切是否按预期工作,请转到项目的属性,然后选择“Java编译器”→“注释处理”→“工厂路径”。应该在那里列出并启用MapStruct处理器JAR。通过编译器插件(见下文)配置的任何处理器选项都应列在“Java编译器”→“注释处理”下。

如果处理器未启动,请检查是否已启用通过M2E的注释处理器配置。为此,请转到“首选项”→“Maven”→“注释处理”,然后选择“自动配置JDT APT”。或者,在propertiesPOM文件部分中指定以下内容<m2e.apt.activation>jdt_apt</m2e.apt.activation>

还要确保您的项目使用的是Java 1.8或更高版本(项目属性→“Java编译器”→“编译合规性级别”)。它不适用于旧版本。

2.2。摇篮

将以下内容添加到Gradle构建文件中以启用MapStruct:

示例2. Gradle配置(3.4及更高版本)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
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}"
}
...
示例3. Gradle(3.3及更早版本)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
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}"
}
...

您可以在GitHub上mapstruct-examples项目中找到完整的示例

2.3。Apache Ant

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

示例4. Ant配置
1
2
3
4
5
6
7
8
9
...
<javac
    srcdir="src/main/java"
    destdir="target/classes"
    classpath="path/to/mapstruct-1.3.0.Final.jar">
    <compilerarg line="-processorpath path/to/mapstruct-processor-1.3.0.Final.jar"/>
    <compilerarg line="-s target/generated-sources"/>
</javac>
...

您可以在GitHub上mapstruct-examples项目中找到完整的示例

2.4。配置选项

可以使用注释处理器选项配置MapStruct代码生成器

直接调用javac时,这些选项将以-Akey = value的形式传递给编译器通过Maven使用MapStruct时,可以使用optionsMaven处理器插件配置中的元素传递任何处理器选项,如下所示:

示例5. Maven配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
<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>
...
示例6. Gradle配置
1
2
3
4
5
6
7
8
...
compileJava {
    options.compilerArgs = [
        '-Amapstruct.suppressGeneratorTimestamp=true',
        '-Amapstruct.suppressGeneratorVersionInfoComment=true'
    ]
}
...

存在以下选项:

表1. MapStruct处理器选项
选项 目的 默认

mapstruct. suppressGeneratorTimestamp

如果设置为true@Generated则抑制在生成的映射器类中的注释中创建时间戳

false

mapstruct. suppressGeneratorVersionInfoComment

如果设置为true则抑制在生成的映射器类comment中的@Generated注释中创建属性注释包含有关MapStruct版本以及用于注释处理的编译器的信息。

false

mapstruct.defaultComponentModel

组件模型的名称(请参阅检索映射器)基于应生成的映射器。

支持的值是:

  • default:映射器不使用组件模型,通常通过检索实例 Mappers#getMapper(Class)

  • cdi:生成的映射器是一个应用程序范围的CDI bean,可以通过它检索 @Inject

  • spring:生成的映射器是一个单例范围的Spring bean,可以通过它检索 @Autowired

  • jsr330:生成的映射器使用{@code @Named}进行注释,并且可以通过@Inject(例如使用Spring)进行检索

如果为特定映射器via提供了组件模型@Mapper#componentModel(),则注释中的值优先。

default

mapstruct.unmappedTargetPolicy

如果未使用源值填充映射方法的目标对象的属性,则应用默认报告策略。

支持的值是:

  • ERROR:任何未映射的目标属性都将导致映射代码生成失败

  • WARN:任何未映射的目标属性都会在构建时发出警告

  • IGNORE:忽略未映射的目标属性

如果为特定映射器via提供了策略@Mapper#unmappedTargetPolicy(),则注释中的值优先。

WARN

2.5。在Java 9上使用MapStruct

MapStruct可以与Java 9(JPMS)一起使用,对它的支持是实验性的。

Java 9的核心主题是JDK的模块化。这样做的一个结果是需要为项目启用特定模块才能使用javax.annotation.Generated注释。@Generated由MapStruct添加到生成的映射器类,以将它们标记为生成的代码,说明生成日期,生成器版本等。

要允许使用@Generated注释,必须启用模块java.xml.ws.annotation使用Maven时,可以这样做:

export MAVEN_OPTS =“ -  add-modules java.xml.ws.annotation”

如果@Generated注释不可用,MapStruct将检测到这种情况,而不是将其添加到生成的映射器中。

在Java 9 java.annotation.processing.Generated中添加了(java.compiler模块的一部分),如果这个注释可用,那么它将被使用。

3.定义映射器

在本节中,您将学习如何使用MapStruct定义bean映射器以及必须执行哪些选项。

3.1。基本映射

要创建映射器,只需使用所需的映射方法定义Java接口,并使用注释对其进行org.mapstruct.Mapper注释:

示例7.用于定义映射器的Java接口
1
2
3
4
5
6
7
8
9
10
@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代码生成器创建的执行CarMapper过程中生成时的界面。

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

  • 当某个属性与其目标实体对应的名称相同时,它将被隐式映射。

  • 当属性在目标实体中具有不同的名称时,可以通过@Mapping注释指定其名称

必须在注释中指定JavaBeans规范中定义的属性名称@Mapping,例如具有访问器方法的属性的seatCountgetSeatCount()setSeatCount()

通过@BeanMapping(ignoreByDefault = true)默认行为将是显式映射,这意味着必须通过此方式指定所有映射,@Mapping并且不会对缺少的目标属性发出警告。

还支持流利的制定者。Fluent setter是与所修改类型返回相同类型的setter。

例如

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

为了更好地理解MapStruct的作用,请查看carToCarDto()MapStruct生成方法的以下实现

示例8. MapStruct生成的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 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将应用自动转换(例如,对于price属性,另请参见隐式类型转换)或者可选地调用/创建另一种映射方法(例如,对于驱动程序) / engine属性,另请参见映射对象引用)。当且仅当source和target属性是Bean的属性并且它们本身是Bean或简单属性时,MapStruct才会创建新的映射方法。即它们不是CollectionMap类型属性。

将通过创建包含source属性中的元素的目标集合类型的新实例来复制具有相同元素类型的集合类型属性。对于具有不同元素类型的集合类型属性,每个元素将单独映射并添加到目标集合中(请参阅映射集合)。

MapStruct会考虑源和目标类型的所有公共属性。这包括在超类型上声明的属性。

3.2。向映射器添加自定义方法

在某些情况下,可能需要手动实现从一种类型到另一种类型的特定映射,这是MapStruct无法生成的。处理此问题的一种方法是在另一个类上实现自定义方法,然后由MapStruct生成的映射器使用该方法(请参阅调用其他映射器)。

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

作为一个例子,我们假设从映射PersonPersonDto需要一些MapStruct无法生成的特殊逻辑。然后,您可以从上一个示例中定义映射器,如下所示:

示例9.使用默认方法定义自定义映射的Mapper
1
2
3
4
5
6
7
8
9
10
11
@Mapper
public interface CarMapper {

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

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

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

映射器也可以以抽象类而不是接口的形式定义,并直接在mapper类中实现自定义方法。在这种情况下,MapStruct将使用所有抽象方法的实现生成抽象类的扩展。这种方法优于声明默认方法的一个优点是可以在mapper类中声明其他字段。

前面的例子,其中从映射PersonPersonDto需要一些特殊的逻辑然后可以定义如下:

示例10.由抽象类定义的映射器
1
2
3
4
5
6
7
8
9
10
11
@Mapper
public abstract class CarMapper {

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

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

MapStruct将生成一个子类,该子类CarMapper具有该carToCarDto()方法的实现,因为它被声明为abstract。生成的代码carToCarDto()personToPersonDto()在映射driver属性调用手动实现的方法

3.3。具有多个源参数的映射方法

MapStruct还支持具有多个源参数的映射方法。例如,为了将多个实体组合成一个数据传输对象,这是有用的。以下是一个示例:

示例11.具有多个源参数的映射方法
1
2
3
4
5
6
7
@Mapper
public interface AddressMapper {

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

所示的映射方法采用两个源参数并返回组合的目标对象。与单参数映射方法一样,属性按名称映射。

如果多个源对象定义具有相同名称的属性,则必须使用@Mapping注释指定从中检索属性的源参数,如示例中的description属性所示。如果不解决这种歧义,将会引发错误。对于在给定源对象中仅存在一次的属性,可以选择指定源参数的名称,因为它可以自动确定。

使用@Mapping注释时,必须指定属性所在的参数

null如果所有源参数都是,则返回具有多个源参数的映射方法null否则,将实例化目标对象,并且将传播来自所提供参数的所有属性。

MapStruct还提供了直接引用源参数的可能性。

示例12.直接引用源参数的映射方法
1
2
3
4
5
6
7
@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.4。更新现有的bean实例

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

示例13.更新方法
1
2
3
4
5
@Mapper
public interface CarMapper {

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

生成的updateCarFromDto()方法代码Car使用给定CarDto对象的属性更新传递的实例可能只有一个参数被标记为映射目标。相反的void,你还可以设置方法的返回类型为目标参数的类型,这将导致生成的实现来更新通过映射目标并返回它。这允许对映射方法进行流畅的调用。

将更新要更新的目标bean的集合或映射类型属性,然后使用相应源集合或映射中的值填充这些属性。

3.5。直接现场访问的映射

MapStruct还支持public没有getter / setter 字段的映射如果无法为属性找到合适的getter / setter方法,MapStruct将使用这些字段作为读/写访问器。

如果字段是public,则字段被视为读取访问器public final如果字段static不被视为读访问者。

只有字段被认为是写访问器public如果字段是final和/或static它不被视为写访问器。

小例子:

示例14.用于映射的示例类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
}

对于上面的配置,生成的映射器如下所示:

示例15.生成的映射器,用于示例类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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();
        // ...
    }
}

您可以 在GitHub上mapstruct-examples-field-mapping项目中找到完整的示例

3.6。使用构建器

MapStruct还支持通过构建器映射不可变类型。执行映射时,MapStruct会检查是否存在要映射的类型的构建器。这是通过BuilderProviderSPI 完成的如果某个类型的构建器存在,则该构建器将用于映射。

默认实现BuilderProvider假设如下:

  • 该类型具有无参数的公共静态构建器创建方法,该方法返回构建器。例如,Person有一个返回的公共静态方法PersonBuilder

  • 构建器类型具有无参数的公共方法(构建方法),它返回正在构建的类型在我们的示例中PersonBuilder有一个返回的方法Person

  • 如果有多个构建方法,MapStruct将查找一个调用的方法build,如果存在这样的方法,则会使用此方法,否则将创建编译错误。

  • 可以通过@Builder在:@BeanMapping@Mapper或中使用来定义特定的构建方法@MapperConfig

  • 如果有多个构建器创建方法满足上述条件,那么MoreThanOneBuilderCreationMethodException 将从DefaultBuilderProviderSPI 抛出a 如果MoreThanOneBuilderCreationMethodExceptionMapStruct将在编译中写入警告而不使用任何构建器。

如果找到这样的类型,则MapStruct将使用该类型执行映射(即,它将查找该类型的setter)。要完成映射,MapStruct会生成将调用构建器的构建方法的代码。

对象的工厂也被认为是构建器类型。例如,如果我们的对象工厂存在,PersonBuilder那么将使用该工厂而不是构建器创建方法。

示例16.具有Builder示例的人员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 );
        }
    }
}
示例17. Person Mapper定义
1
2
3
4
public interface PersonMapper {

    Person map(PersonDto dto);
}
示例18.使用构建器生成的映射器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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

  • AutoValue

  • 不可变 - 当注释处理器路径上存在不可变项,默认情况下将使用ImmutablesAccessorNamingStrategyImmutablesBuilderProvider

  • FreeBuilder - 当注释处理器路径上存在FreeBuilderFreeBuilderAccessorNamingStrategy,默认情况下将使用它。使用FreeBuilder时,应遵循JavaBean约定,否则MapStruct将无法识别流畅的getter。

  • 如果实现支持默认的已定义规则,它也适用于自定义构建器(手写的构建器)BuilderProvider否则,您需要编写自定义BuilderProvider

如果您想要使用构建器禁用,则可以使用目录中NoOpBuilderProvider创建org.mapstruct.ap.spi.BuilderProvider文件作为其内容。META-INF/servicesorg.mapstruct.ap.spi.NoOpBuilderProvider

4.检索映射器

4.1。Mappers工厂

可以通过org.mapstruct.factory.Mappers检索Mapper实例只需调用该getMapper()方法,传递mapper的接口类型即可返回:

示例19.使用Mappers工厂
1
CarMapper mapper = Mappers.getMapper( CarMapper.class );

按照惯例,映射器接口应该定义一个名为的成员INSTANCE该成员包含mapper类型的单个实例:

示例20.声明映射器的实例
1
2
3
4
5
6
7
@Mapper
public interface CarMapper {

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

    CarDto carToCarDto(Car car);
}

此模式使客户端可以非常轻松地使用映射器对象,而无需重复实例化新实例:

示例21.访问映射器
1
2
Car car = ...;
CarDto dto = CarMapper.INSTANCE.carToCarDto( car );

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

4.2。使用依赖注入

如果您正在使用依赖注入框架(如CDI(Java TM EE的上下文和依赖注入)或Spring Framework),建议通过依赖注入获取映射器对象。为此,您可以指定生成映射器类的组件模型应基于通过@Mapper#componentModel或使用处理器选项,如配置选项中所述

目前支持CDI和Spring(后者通过其自定义注释或使用JSR 330注释)。有关属性的允许值,请参阅配置选项,这些值componentModelmapstruct.defaultComponentModel处理器选项相同在这两种情况下,所需的注释都将添加到生成的映射器实现类中,以便使相同的主题依赖注入。以下显示了使用CDI的示例:

示例22.使用CDI组件模型的映射器
1
2
3
4
5
@Mapper(componentModel = "cdi")
public interface CarMapper {

    CarDto carToCarDto(Car car);
}

生成的映射器实现将使用@ApplicationScoped注释进行标记,因此可以使用@Inject注释将其注入到字段,构造函数参数等中

示例23.通过依赖注入获取映射器
1
2
@Inject
private CarMapper mapper;

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

4.3。注射策略

使用依赖项注入时,可以在字段和构造函数注入之间进行选择。这可以通过提供注射策略@Mapper@MapperConfig注释来完成。

示例24.使用构造函数注入
1
2
3
4
@Mapper(componentModel = "cdi", uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CarMapper {
    CarDto carToCarDto(Car car);
}

生成的映射器将注入uses属性中定义的所有类InjectionStrategy#CONSTRUCTOR被使用时,构造函数都会有相应的注释和字段不会。InjectionStrategy#FIELD被使用时,注释是在球场上本身。目前,默认的注入策略是现场注入。建议使用构造函数注入来简化测试。

对于抽象类或装饰器,应该使用setter注入。

5.数据类型转换

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

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

在本节中,您将了解MapStruct如何处理此类数据类型转换。

5.1。隐式类型转换

在许多情况下,MapStruct会自动处理类型转换。例如,如果属性int在源bean中是类型但String在目标bean中是类型,则生成的代码将分别通过调用String#valueOf(int)透明地执行转换Integer#parseInt(String)

目前,会自动应用以下转化:

  • 之间的所有Java基本数据类型及其相应的包装类型,例如之间intIntegerbooleanBoolean等生成的代码是null转换一个包装型成相应的原始类型时一个感知,即,null检查将被执行。

  • 所有Java基本号码类型和包装类型,例如之间intlongbyteInteger

从较大的数据类型转换成更小的(例如,从longint)可能会导致一个值或精确度的损失。MapperMapperConfig注释有一个方法typeConversionPolicy来控制警告/错误。由于向后兼容性原因,默认值为“ReportingPolicy.IGNORE”。

  • 所有Java基本类型之间(包括其包装)和String之间,例如intStringBooleanStringjava.text.DecimalFormat可以指定理解的格式字符串

示例25.从int到String的转换
1
2
3
4
5
6
7
8
9
@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可以指定理解的格式字符串

示例26.从BigDecimal到String的转换
1
2
3
4
5
6
7
@Mapper
public interface CarMapper {

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

}
  • 之间JAXBElement<T>TList<JAXBElement<T>>List<T>

  • 介于java.util.Calendar/ java.util.Date和JAXB 之间XMLGregorianCalendar

  • 之间java.util.Date/ XMLGregorianCalendarStringjava.text.SimpleDateFormat可以通过dateFormat选项指定理解的格式字符串,如下所示:

示例27.从Date到String的转换
1
2
3
4
5
6
7
8
9
@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);
}
  • Jodas之间org.joda.time.DateTimeorg.joda.time.LocalDateTimeorg.joda.time.LocalDateorg.joda.time.LocalTimeStringjava.text.SimpleDateFormat可以通过dateFormat选项指定理解的格式字符串(参见上文)。

  • Jodas之间org.joda.time.DateTimejavax.xml.datatype.XMLGregorianCalendarjava.util.Calendar

  • Jodas之间org.joda.time.LocalDateTimeorg.joda.time.LocalDatejavax.xml.datatype.XMLGregorianCalendarjava.util.Date

  • 之间java.time.ZonedDateTimejava.time.LocalDateTimejava.time.LocalDatejava.time.LocalTime从Java 8日期时间包装和Stringjava.text.SimpleDateFormat可以通过dateFormat选项指定理解的格式字符串(参见上文)。

  • 之间java.time.Instantjava.time.Durationjava.time.Period从Java 8日期时间封装和String使用parse中的每个类的方法从映射String,并使用toString映射到String

  • java.time.ZonedDateTimeJava 8 Date-Time包之间java.util.Date,当ZonedDateTime从给定映射a Date时,使用系统默认时区。

  • java.time.LocalDateTimeJava 8 Date-Time包和java.util.Date时区UTC用作时区之间。

  • java.time.LocalDateJava 8 Date-Time包和java.util.Date/之间java.sql.Date使用时区UTC作为时区。

  • 之间java.time.Instant从Java 8日期时间包装和java.util.Date

  • 之间java.time.ZonedDateTime从Java 8日期时间包装和java.util.Calendar

  • 之间java.sql.Datejava.util.Date

  • 之间java.sql.Timejava.util.Date

  • 之间java.sql.Timestampjava.util.Date

  • 从a String省略转换时Mapping#dateFormat,会导致使用默认语言环境的默认模式和日期格式符号。此规则的一个例外是根据XML Schema 1.0 Part 2,Section 3.2.7-14.1,Lexical RepresentationXmlGregorianCalendar进行解析String

  • 之间java.util.CurrencyString

    • 从a转换时String,该值必须是有效的ISO-4217字母代码,否则IllegalArgumentException抛出

5.2。映射对象引用

通常,对象不仅具有原始属性,还具有引用其他对象的功能。例如,Car类可以包含Person对象的引用(表示汽车的驱动程序),该PersonDto对象应该映射到CarDto引用对象

在这种情况下,只需为引用的对象类型定义一个映射方法:

示例28.使用另一种映射方法的映射器
1
2
3
4
5
6
7
@Mapper
public interface CarMapper {

    CarDto carToCarDto(Car car);

    PersonDto personToPersonDto(Person person);
}

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

这样就可以映射任意深度对象图。从实体映射到数据传输对象时,在某个点切割对其他实体的引用通常很有用。为此,请实现自定义映射方法(请参阅下一节),例如将引用的实体映射到目标对象中的id。

在生成映射方法的实现时,MapStruct将对源和目标对象中的每个属性对应用以下例程:

  • 如果source和target属性具有相同的类型,则该值将简单地从源复制到目标。如果属性是集合(例如a List),则集合的副本将被设置到目标属性中。

  • 如果源属性和目标属性类型不同,请检查是否存在另一种映射方法,该方法将源属性的类型作为参数类型,并将目标属性的类型作为返回类型。如果存在这样的方法,则将在生成的映射实现中调用它。

  • 如果不存在此类方法,MapStruct将查看是否存在属性的源类型和目标类型的内置转换。如果是这种情况,生成的映射代码将应用此转换。

  • 如果没有找到这样的方法,MapStruct将尝试生成一个自动子映射方法,该方法将执行源和目标属性之间的映射。

  • 如果MapStruct无法创建基于名称的映射方法,则会在构建时引发错误,指示不可映射的属性及其路径。

为了阻止MapStruct生成自动子映射方法,可以使用@Mapper( disableSubMappingMethodsGeneration = true )

在生成自动子映射方法期间,不会考虑共享配置关注问题#1086以获取更多信息。

5.3。控制嵌套的bean映射

如上所述,MapStruct将基于源和目标属性的名称生成方法。不幸的是,在许多情况下这些名称不匹配。

'。' @Mapping源名称或目标类型中的表示法可用于控制名称不匹配时应如何映射属性。我们的示例存储库中有一个精心设计的示例,用于解释如何克服此问题。

在最简单的场景中,嵌套级别上的属性需要更正。例如fish,在FishTankDto和中具有相同名称的属性FishTank对于此属性,MapStruct会自动生成映射:FishDto fishToFishDto(Fish fish)MapStruct不可能意识到偏离的属性kindtype因此,这可以在映射规则中解决:@Mapping(target="fish.kind", source="fish.type")这告诉MapStruct不要kind在此级别查找名称并将其映射到type

示例29.映射器控制嵌套bean映射I
1
2
3
4
5
6
7
8
9
10
@Mapper
public interface FishTankMapper {

    @Mapping(target = "fish.kind", source = "fish.type")
    @Mapping(target = "fish.name", ignore = true)
    @Mapping(target = "ornament", source = "interior.ornament")
    @Mapping(target = "material.materialType", source = "material")
    @Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
    FishTankDto map( FishTank source );
}

可以使用相同的构造来忽略嵌套级别的某些属性,如第二个@Mapping规则中所示。

当源和目标不共享相同的嵌套级别(相同数量的属性)时,MapStruct甚至可以用于“樱桃挑选”属性。这可以在源 - 和目标类型中完成。这将在接下来的两条规则中得到证明:@Mapping(target="ornament", source="interior.ornament")@Mapping(target="material.materialType", source="material")

当映射首先共享一个共同基础时,甚至可以完成后者。例如:共享相同名称的所有属性Quality都映射到QualityDto同样,所有属性Report都映射到ReportDto,但有一个例外:organisationin OrganisationDto为空(因为源级别没有组织)。只有the name填充了organisationNamefrom Report这证明了@Mapping(target="quality.report.organisation.name", source="quality.report.organisationName")

再回到原来的例子:如果kindtype将豆子自己?在这种情况下,MapStruct将再次生成继续映射的方法。这在下一个例子中得到证明:

示例30.映射器控制嵌套bean映射II
1
2
3
4
5
6
7
8
9
10
11
12
13
@Mapper
public interface FishTankMapperWithDocument {

    @Mapping(target = "fish.kind", source = "fish.type")
    @Mapping(target = "fish.name", expression = "java(\"Jaws\")")
    @Mapping(target = "plant", ignore = true )
    @Mapping(target = "ornament", ignore = true )
    @Mapping(target = "material", ignore = true)
    @Mapping(target = "quality.document", source = "quality.report")
    @Mapping(target = "quality.document.organisation.name", constant = "NoIdeaInc" )
    FishTankWithNestedDocumentDto map( FishTank source );

}

注意发生了什么@Mapping(target="quality.document", source="quality.report")DocumentDto在目标方面不存在。它映射自ReportMapStruct继续在此生成映射代码。该映射本身可以引导到另一个名称。这甚至适用于常量和表达式。最后一个例子中显示了:@Mapping(target="quality.document.organisation.name", constant="NoIdeaInc")

MapStruct将对源中的每个嵌套属性执行空检查。

我们鼓励用户显式编写自己的嵌套方法,而不是通过父方法配置所有内容。这将嵌套映射的配置放在一个位置(方法),在这里可以从上层的几个方法重用它,而不是在所有这些上层方法上重新配置相同的东西。

在某些情况下ReportingPolicy,将用于生成的嵌套方法IGNORE这意味着MapStruct可能无法在嵌套映射中报告未映射的目标属性。

5.4。调用其他映射器

除了在同一映射器类型上定义的方法之外,MapStruct还可以调用其他类中定义的映射方法,无论是MapStruct生成的映射器还是手写映射方法。这对于在多个类中构建映射代码(例如,每个应用程序模块使用一个映射器类型)或者如果要提供MapStruct无法生成的自定义映射逻辑非常有用。

例如,Car类可能包含一个属性,manufacturingDate而相应的DTO属性是String类型。为了映射这个属性,你可以像这样实现一个mapper类:

示例31.手动实现的映射器类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DateMapper {

    public String asString(Date date) {
        return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
            .format( date ) : null;
    }

    public Date asDate(String date) {
        try {
            return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
                .parse( date ) : null;
        }
        catch ( ParseException e ) {
            throw new RuntimeException( e );
        }
    }
}

接口@Mapper注释中CarMapper引用DateMapper类如下:

示例32.引用另一个映射器类
1
2
3
4
5
@Mapper(uses=DateMapper.class)
public class CarMapper {

    CarDto carToCarDto(Car car);
}

在为carToCarDto()方法的实现生成代码时,MapStruct将寻找一种方法,该方法将Date对象映射到String中,在DateMapper上找到它并生成asString()用于映射manufacturingDate属性的调用

生成的映射器使用为其配置的组件模型检索引用的映射器。如果例如CDI用作组件模型CarMapper,则DateMapper也必须是CDI bean。使用默认组件模型时,MapStruct生成的映射器要引用的任何手写映射器类必须声明一个公共的无参数构造函数才能实例化。

5.5。将映射目标类型传递给自定义映射器

将自定义映射器连接到生成的映射器时@Mapper#uses()Class可以在自定义映射方法中定义其他类型的参数(或其超类型),以便为特定目标对象类型执行常规映射任务。必须使用@TargetTypeMapStruct 注释该属性,以生成传递Class表示目标bean的相应属性类型实例的调用

例如,CarDto可以具有包含实体主键的owner类型属性您现在可以创建一个通用的自定义映射器,将任何对象解析为其对应的托管JPA实体实例。ReferencePersonReference

示例33.期望映射目标类型作为参数的映射方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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将生成如下内容:

示例34.生成的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//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;
    }
}

5.6。将上下文或状态对象传递给自定义方法

可以通过生成的映射方法将附加上下文状态信息传递给具有@Context参数的自定义方法这些参数在适用时传递给其他映射方法,@ObjectFactory方法(请参阅对象工厂)或@BeforeMapping/ @AfterMapping方法(请参阅使用映射前映射和映射后方法映射自定义),因此可以在自定义代码中使用。

@Context搜索参数的@ObjectFactory方法,如果适用,则在提供的上下文参数值上调用这些方法。

@Context还会搜索@BeforeMapping/ @AfterMappingmethods的参数,如果适用,将在提供的上下文参数值上调用这些参数。

注意:null在上下文参数的映射方法之前/之后调用之前,执行任何检查。调用者需要确保null在这种情况下不通过。

对于生成的代码来调用使用@Context参数声明的方法,生成的映射方法的声明也需要包含至少那些(或可指定的)@Context参数。生成的代码不会创建缺少@Context参数的新实例,也不会传递文字null

示例35.使用@Context参数将数据传递给手写属性映射方法
1
2
3
4
5
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将生成如下内容:

示例36.生成的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
//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;
}

5.7。映射方法解析

将属性从一种类型映射到另一种类型时,MapStruct会查找将源类型映射到目标类型的最具体方法。该方法可以在相同的映射器接口上声明,也可以在通过其注册的另一个映射器上声明@Mapper#uses()这同样适用于工厂方法(请参阅对象工厂)。

用于查找映射或工厂方法的算法尽可能类似于Java的方法解析算法。特别是,具有更具体源类型的方法将优先(例如,如果有两种方法,一种映射搜索到的源类型,另一种映射超类型的方法)。如果找到多个最具体的方法,将引发错误。

使用JAXB时,例如,在将a转换String为相应的时JAXBElement<String>,MapStruct会在查找映射方法时考虑注释scopename属性@XmlElementDecl这可确保创建的JAXBElement实例具有正确的QNAME值。您可以在此处找到一个映射JAXB对象的测试

5.8。基于限定符的映射方法选择

在许多情况下,需要具有相同方法签名(除了名称)的具有不同行为的映射方法。MapStruct有一个便利的机制来处理这种情况:@Qualifierorg.mapstruct.Qualifier)。“限定符”是用户可以编写的自定义注释,“坚持”作为使用的映射器包含的映射方法,并且可以在bean属性映射,可迭代映射或映射映射中引用。多个限定符可以“粘贴”到方法和映射上。

所以,让我们说有一个手写的方法来映射带有String返回类型和String参数的标题,其中许多其他引用的映射器具有相同的String返回类型 - String参数签名:

示例37.具有相同源和目标类型的几种映射方法
1
2
3
4
5
6
7
8
9
10
public class Titles {

    public String translateTitleEG(String title) {
        // some mapping logic
    }

    public String translateTitleGE(String title) {
        // some mapping logic
    }
}

还有一个使用这个手写映射器的映射器,其中源和目标具有应该映射的属性“title”:

示例38.映射器导致模糊的映射方法错误
1
2
3
4
5
6
@Mapper( uses = Titles.class )
public interface MovieMapper {

     GermanRelease toGerman( OriginalRelease movies );

}

如果不使用限定符,这将导致模糊的映射方法错误,因为找到了2个限定方法(translateTitleEGtranslateTitleGE),而MapStruct没有提示选择哪一个。

输入限定符方法:

示例39.声明限定符类型
1
2
3
4
5
6
7
import org.mapstruct.Qualifier;

@Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface TitleTranslator {
}

并且,一些限定符用于指示使用哪个翻译器从源语言映射到目标语言:

示例40.声明映射方法的限定符类型
1
2
3
4
5
6
7
import org.mapstruct.Qualifier;

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface EnglishToGerman {
}
1
2
3
4
5
6
7
import org.mapstruct.Qualifier;

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface GermanToEnglish {
}

请把保留的音符TitleTranslator上一流水平,EnglishToGermanGermanToEnglish在方法的水平!

然后,使用限定符,映射可能如下所示:

示例41.使用限定符的映射器
1
2
3
4
5
6
7
@Mapper( uses = Titles.class )
public interface MovieMapper {

     @Mapping( target = "title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } )
     GermanRelease toGerman( OriginalRelease movies );

}
示例42.自定义映射器限定它提供的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
@TitleTranslator
public class Titles {

    @EnglishToGerman
    public String translateTitleEG(String title) {
        // some mapping logic
    }

    @GermanToEnglish
    public String translateTitleGE(String title) {
        // some mapping logic
    }
}

请确保使用的保留策略等于保留策略CLASS@Retention(RetentionPolicy.CLASS))。

使用限定符注释的类/方法将不再符合没有该qualifiedBy元素的映射

bean映射上也存在相同的机制@BeanMapping#qualifiedBy::它选择用指示的限定符标记的工厂方法。

在许多情况下,声明一个新的注释以帮助选择过程对于您尝试实现的目标来说可能太多了。对于这些情况,MapStruct具有@Named注释。此注释是预定义的限定符(使用@Qualifier自身注释),可用于命名Mapper,或者更直接地通过其值来定义映射方法。上面的例子如下:

示例43.自定义映射器,通过方式注释要限定的方法 @Named
1
2
3
4
5
6
7
8
9
10
11
12
13
@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
    }
}
例44.使用named的Mapper
1
2
3
4
5
6
7
@Mapper( uses = Titles.class )
public interface MovieMapper {

     @Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } )
     GermanRelease toGerman( OriginalRelease movies );

}

虽然使用的机制是相同的,但用户必须更加小心。在IDE中重构已定义限定符的名称也会巧妙地重构所有其他出现的事件。这显然不是更改名称的情况。

6.映射集合

集合类型(的映射ListSet等等)以相同的方式映射bean类型,通过定义与在映射器接口所需的源和目标类型的映射方法,即完成。MapStruct支持Java Collection Framework中的各种可迭代类型

生成的代码将包含一个循环,该循环遍历源集合,转换每个元素并将其放入目标集合中。如果在给定的映射器或它使用的映射器中找到集合元素类型的映射方法,则调用此方法以执行元素转换。或者,如果存在源元素类型和目标元素类型的隐式转换,则将调用此转换例程。以下是一个示例:

示例45.具有集合映射方法的映射器
1
2
3
4
5
6
7
8
9
@Mapper
public interface CarMapper {

    Set<String> integerSetToStringSet(Set<Integer> integers);

    List<CarDto> carsToCarDtos(List<Car> cars);

    CarDto carToCarDto(Car car);
}

将所生成的执行的integerSetToStringSet执行从转换IntegerString每个元件,而所产生的carsToCarDtos()方法调用carToCarDto()用于如下面所示的每个包含的元素的方法:

示例46.生成的集合映射方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//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;
}

请注意,当映射bean的集合类型属性(例如从Car#passengers(类型List<Person>)到CarDto#passengers(类型List<PersonDto>时,MapStruct将查找具有匹配参数和返回类型的集合映射方法

例47.使用集合映射方法映射bean属性
1
2
3
//GENERATED CODE
carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) );
...

有些框架和库只公开JavaBeans getter,但没有sets类型属性的setter。默认情况下,使用JAXB从XML模式生成的类型遵循此模式。在这种情况下,用于映射此类属性的生成代码将调用其getter并添加所有映射元素:

示例48.用于集合映射的添加方法的使用
1
2
3
//GENERATED CODE
carDto.getPassengers().addAll( personsToPersonDtos( car.getPassengers() ) );
...

不允许使用可迭代源和不可迭代目标声明映射方法,反之亦然。检测到这种情况时会出错。

6.1。映射地图

还支持基于映射的映射方法。以下是一个示例:

例49.映射映射方法
1
2
3
4
5
public interface SourceTargetMapper {

    @MapMapping(valueDateFormat = "dd.MM.yyyy")
    Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}

与可迭代映射类似,生成的代码将遍历源映射,转换每个值和键(通过隐式转换或通过调用另一个映射方法)并将它们放入目标映射中:

示例50.生成的映射映射方法的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//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;
}

6.2。集合映射策略

MapStruct有CollectionMappingStrategy,与可能的值:ACCESSOR_ONLYSETTER_PREFERREDADDER_PREFERREDTARGET_IMMUTABLE

在下表中,破折号-表示属性名称。接下来,尾随s表示复数形式。该表说明了选择和它们是如何施加到的存在/由于缺少set-sadd-和/或get-s在目标对象上的方法:

表2.集合映射策略选项
选项 只有目标set-s可用 只有目标添加 - 可用 set-s / add-都可用 没有set-s / add-可用 现有目标(@TargetType

ACCESSOR_ONLY

设置-S

得到-S

设置-S

得到-S

得到-S

SETTER_PREFERRED

设置-S

加-

设置-S

得到-S

得到-S

ADDER_PREFERRED

设置-S

加-

加-

得到-S

得到-S

TARGET_IMMUTABLE

设置-S

例外

设置-S

例外

设置-S

一些背景:一种adder方法通常用于生成(JPA)实体的情况,以将单个元素(实体)添加到底层集合。调用加法器在parent(调用加法器的bean(实体))和子(ren)(集合中的元素(实体))之间建立父子关系。为了找到合适的adder,MapStruct将尝试在底层集合的泛型参数类型和候选集的单个参数之间进行匹配adder当有更多候选者时,复数setter/ getter名称将转换为单数,并且除了进行匹配之外还将使用。

DEFAULT不应明确使用该选项它用于区分显式用户希望覆盖a @MapperConfig中的隐式Mapstruct选项中的默认值@Mapper该选项DEFAULT是同义词ACCESSOR_ONLY

使用adder方法和JPA实体时,Mapstruct假定使用集合实现(例如,a ArrayList初始化目标集合您可以使用工厂创建具有初始化集合的新目标实体,而不是通过其构造函数创建目标实体的Mapstruct。

6.3。用于集合映射的实现类型

当iterable或map映射方法将接口类型声明为返回类型时,其实现类型之一将在生成的代码中实例化。下表显示了在生成的代码中实例化的受支持的接口类型及其相应的实现类型:

表3.集合映射实现类型
接口类型 实施类型

Iterable

ArrayList

Collection

ArrayList

List

ArrayList

Set

HashSet

SortedSet

TreeSet

NavigableSet

TreeSet

Map

HashMap

SortedMap

TreeMap

NavigableMap

TreeMap

ConcurrentMap

ConcurrentHashMap

ConcurrentNavigableMap

ConcurrentSkipListMap

7.映射流

映射java.util.Stream是以与集合类型的映射类似的方式完成的,即通过在映射器接口中定义具有所需源和目标类型的映射方法。

生成的代码将包含Stream从提供的Iterable/数组创建的一个或将收集提供StreamIterable/数组中。如果存在源和目标元素类型的映射方法或隐式转换,则将完成此转换Stream#map()以下是一个示例:

例51.具有流映射方法的映射器
1
2
3
4
5
6
7
8
9
@Mapper
public interface CarMapper {

    Set<String> integerStreamToStringSet(Stream<Integer> integers);

    List<CarDto> carsToCarDtos(Stream<Car> cars);

    CarDto carToCarDto(Car car);
}

将所生成的执行的integerStreamToStringSet()执行从转换IntegerString每个元件,而所产生的carsToCarDtos()方法调用carToCarDto()用于如下面所示的每个包含的元素的方法:

示例52.生成的流映射方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//GENERATED CODE
@Override
public Set<String> integerStreamToStringSet(Stream<Integer> integers) {
    if ( integers == null ) {
        return null;
    }

    return integers.stream().map( integer -> String.valueOf( integer ) )
        .collect( Collectors.toCollection( HashSet<String>::new ) );
}

@Override
public List<CarDto> carsToCarDtos(Stream<Car> cars) {
    if ( cars == null ) {
        return null;
    }

    return integers.stream().map( car -> carToCarDto( car ) )
        .collect( Collectors.toCollection( ArrayList<CarDto>::new ) );
}

如果执行从a Stream到a Iterable或数组的映射,则传递Stream将被消耗,并且将不再可以使用它。

同样实现类型中用于集合映射实现类型都在做的时候用于创建的集合StreamIterable映射。

8.映射值

8.1。映射枚举类型

MapStruct支持生成将一个Java枚举类型映射到另一个Java枚举类型的方法。

默认情况下,源枚举中的每个常量都映射到目标枚举类型中具有相同名称的常量。如果需要,可以借助@ValueMapping注释将源枚举中的常量映射到具有其他名称的常量源枚举中的几个常量可以映射到目标类型中的相同常量。

以下是一个示例:

例53.枚举映射方法
1
2
3
4
5
6
7
8
9
10
11
12
@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);
}
例54.枚举映射方法结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 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_;
    }
}

默认情况下,如果源枚举类型的常量没有目标类型中具有相同名称的对应常量,并且未映射到另一个常量via,则MapStruct将引发错误@ValueMapping这可确保以安全且可预测的方式映射所有常量。如果由于某种原因发生了无法识别的源值,则生成的映射方法将抛出IllegalStateException。

MapStruct还具有将任何剩余(未指定)映射映射到默认值的机制。这只能在一组值映射中使用一次。它有两种口味:<ANY_REMAINING><ANY_UNMAPPED>

在源的情况下,<ANY_REMAINING>MapStruct将继续将源枚举常量映射到具有相同名称的目标枚举常量。源枚举常量的其余部分将映射到@ValueMappingwith <ANY_REMAINING>source中指定的目标

MapStruct 不会尝试这种基于名称的映射,<ANY_UNMAPPED>并直接将@ValueMappingwith <ANY_UNMAPPED>source中指定的目标应用于余数。

MapStruct能够通过关键字处理null源和null目标<NULL>

适用于该类的常量<ANY_REMAINING><ANY_UNMAPPED>并且<NULL>可在MappingConstants该类中使用。

最后@InheritInverseConfiguration,并@InheritConfiguration可以与组合使用@ValueMappings

例55.枚举映射方法,<NULL>和<ANY_REMAINING>
1
2
3
4
5
6
7
8
9
10
11
12
@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);
}
例56.枚举映射方法结果,<NULL>和<ANY_REMAINING>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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_;
    }
}

注意: MapStruct将避免映射RETAIL和使用B2B何时<ANY_UNMAPPED>代替<ANY_REMAINING>

通过@Mapping注释将枚举映射到枚举DEPRECATED它将从MapStruct的未来版本中删除。请调整现有的枚举映射方法来@ValueMapping代替使用

9.对象工厂

默认情况下,生成的用于将一种bean类型映射到另一种bean或更新bean的代码将调用默认构造函数来实例化目标类型。

或者,您可以插入自定义对象工厂,这些工厂将被调用以获取目标类型的实例。一个用例是JAXB,它创建ObjectFactory用于获取模式类型的新实例的类。

要使用自定义工厂@Mapper#uses(),请按照调用其他映射器中的说明注册它们,或者直接在映射器中实现它们。在创建bean映射的目标对象时,MapStruct将查找无参数方法,使用注释@ObjectFactory的方法或只有一个@TargetType返回所需目标类型的参数的方法,并调用此方法而不是调用默认构造函数:

例57.自定义对象工厂
1
2
3
4
5
6
public class DtoFactory {

     public CarDto createCarDto() {
         return // ... custom factory logic
     }
}
1
2
3
4
5
6
public class EntityFactory {

     public <T extends BaseEntity> T createEntity(@TargetType Class<T> entityClass) {
         return // ... custom factory logic
     }
}
1
2
3
4
5
6
7
8
9
@Mapper(uses= { DtoFactory.class, EntityFactory.class } )
public interface CarMapper {

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

    CarDto carToCarDto(Car car);

    Car carDtoToCar(CarDto carDto);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//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;
    }
}
示例58.具有更新方法的自定义对象工厂
1
2
3
4
5
6
7
8
9
@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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//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
    }
}

此外,使用工厂方法注释@ObjectFactory可以访问映射源。源对象可以作为参数添加,其方式与映射方法相同。@ObjectFactory 为了让MapStruct知道给定的方法只是一个工厂方法注释是必要的。

例59.自定义对象工厂 @ObjectFactory
1
2
3
4
5
6
7
public class DtoFactory {

     @ObjectFactory
     public CarDto createCarDto(Car car) {
         return // ... custom factory logic
     }
}

10.高级映射选项

本章介绍了几个高级选项,可以根据需要微调生成的映射代码的行为。

10.1。默认值和常量

如果相应的源属性为,则可以指定默认值以将预定义值设置为目标属性null可以指定常量以在任何情况下设置这样的预定义值。默认值和常量指定为String值。当目标类型是基元类型或盒装类型时,String值是文字的。在这种情况下允许位/八进制/十进制/十六进制模式,只要它们是有效的文字。在所有其他情况下,常量或默认值可通过内置转换或其他映射方法的调用进行类型转换,以匹配目标属性所需的类型。

具有常量的映射不得包含对source属性的引用。以下示例显示了使用默认值和常量的一些映射:

例60.使用默认值和常量的映射方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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);
}

如果s.getStringProp() == null,则将目标属性stringProperty设置为"undefined"而不是应用该值s.getStringProp()如果s.getLongProperty() == null,则将target属性longProperty设置为-1String "Constant Value"按原样设置为target属性stringConstant该值"3001"被类型转换为Longtarget属性(包装器)类longWrapperConstant日期属性还需要日期格式。该常量"jack-jill-tom"演示了如何StringListMapper调用手写类来将以破折号分隔的列表映射到a List<String>

10.2。表达式

通过表达式,可以包含来自多种语言的结构。

目前只支持Java作为语言。此功能对于调用构造函数非常有用。整个源对象可在表达式中使用。应该注意只插入有效的Java代码:MapStruct不会在生成时验证表达式,但在编译期间会在生成的类中显示错误。

下面的示例演示了如何将两个源属性映射到一个目标:

例61.使用表达式的映射方法
1
2
3
4
5
6
7
8
9
@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.

Example 62. Declaring an import
1
2
3
4
5
6
7
8
9
10
11
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);
}

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

下面的示例演示了如何将两个源属性映射到一个目标:

例63.使用默认表达式的映射方法
1
2
3
4
5
6
7
8
9
10
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);
}

该示例演示了ID如果源字段为null,如何使用defaultExpression设置字段,如果设置sourceId了源对象,则可以使用它来获取现有对象,如果不是,则创建新对象Id请注意,指定了完全限定的包名称,因为MapStruct不会处理UUID的导入(除非在其中明确使用SourceTargetMapper)。这可以通过在@Mapper注释上定义导入来解决(参见表达式)。

10.4。确定结果类型

当结果类型具有继承关系时,选择mapping method(@Mapping)或factory method(@BeanMapping)可能会变得模糊不清。假设一个Apple和一个Banana,它们都是Fruit的特色。

例64.指定bean映射方法的结果类型
1
2
3
4
5
6
7
@Mapper( uses = FruitFactory.class )
public interface FruitMapper {

    @BeanMapping( resultType = Apple.class )
    Fruit map( FruitDto source );

}
1
2
3
4
5
6
7
8
9
10
public class FruitFactory {

    public Apple createApple() {
        return new Apple( "Apple" );
    }

    public Banana createBanana() {
        return new Banana( "Banana" );
    }
}

那么,哪个Fruit必须在映射方法中分解Fruit map(FruitDto source);A Banana还是Apple这是@BeanMapping#resultType派上用场的。它控制工厂方法以选择或缺少工厂方法,以创建返回类型。

映射中存在相同的机制:@Mapping#resultType并且工作方式与您期望的一样:它在存在时选择具有所需结果类型的映射方法。

该机制也存在于可迭代映射和映射映射中。@IterableMapping#elementTargetType用于在结果中选择具有所需元素的映射方法Iterable为了@MapMapping类似的目的,通过#MapMapping#keyTargetType和服务MapMapping#valueTargetType

10.5。控制'null'参数的映射结果

当映射方法的source参数等于时,MapStruct提供对要创建的对象的控制null默认情况下null将返回。

但是,通过指定nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT@BeanMapping@IterableMapping@MapMapping,或全局上@Mapper@MappingConfig,映射结果可以被改变以返回空默认值。这意味着:

  • Bean映射:将返回一个“空”目标bean,除常量和表达式外,它们将在存在时填充。

  • 基元:将返回基的默认值,例如falsefor boolean0for int

  • Iterables / Arrays:将返回一个空的iterable。

  • 地图:将返回空地图。

该策略以分层方式运作。nullValueMappingStrategy映射方法级别的设置将覆盖@Mapper#nullValueMappingStrategy@Mapper#nullValueMappingStrategy并将覆盖@MappingConfig#nullValueMappingStrategy

10.6。控制bean映射中“null”属性的映射结果(仅限更新映射方法)。

@MappingTarget当source属性等于null或者presence检查方法导致'absent' 时,MapStruct提供对要在带注释的目标bean中设置的属性的控制

默认情况下,target属性将设置为null。

然而:

  1. 通过指定nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT@Mapping@BeanMapping@Mapper@MappingConfig,映射结果可以改变返回默认值。对于ListMapStruct产生ArrayList,对于Map一个HashMap,对数组空数组,对于String ""和用于原始/盒装类型的表示false0对于所有其他对象,将创建一个新实例。请注意,需要默认构造函数。如果没有,请使用@Mapping#defaultValue

  2. 通过指定nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE@Mapping@BeanMapping@Mapper@MappingConfig,所述映射的结果将是等于原值@MappingTarget注解目标。

该策略以分层方式运作。Mapping#nullValuePropertyMappingStrategy映射级别nullValuePropertyMappingStrategy上的设置将覆盖映射方法级别将覆盖@Mapper#nullValuePropertyMappingStrategy@Mapper#nullValuePropertyMappingStrategy并将覆盖@MappingConfig#nullValuePropertyMappingStrategy

某些类型的映射(集合,映射),其中指示MapStruct使用getter或adder作为目标访问器,请参阅CollectionMappingStrategyMapStruct将始终生成源属性null检查,无论其值如何,NullValuePropertyMappingStrategy以避免添加null到目标集合或地图。由于假定目标已初始化,因此不会应用此策略。

NullValuePropertyMappingStrategy当presense检查器返回时也适用not present

10.7。控制bean映射中'null'属性的检查结果

MapStruct可以控制何时生成null检查。默认情况下(nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSIONnull将生成一个检查:

  • 当目标是原始的而源不是源时,将源值直接设置为目标值。

  • 应用类型转换,然后:

    1. 在目标上调用setter。

    2. 调用另一个类型转换,然后调用目标上的setter。

    3. 调用映射方法,然后调用目标上的setter。

首先在source属性上调用映射方法不受null检查的保护。因此,生成的映射方法将在对源属性执行映射之前进行空检查。手写映射方法必须处理空值检查。他们有可能添加“意义” null例如:映射null到默认值。

nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS除非源bean上定义了源存在检查器,否则该选项将始终包含源非非原始时的空检查。

该策略以分层方式运作。@Mapping#nullValueCheckStrategy将覆盖@BeanMapping#nullValueCheckStrategy@BeanMapping#nullValueCheckStrategy将覆盖@Mapper#nullValueCheckStrategy@Mapper#nullValueCheckStrategy将覆盖@MappingConfig#nullValueCheckStrategy

10.8。来源存在检查

一些框架生成具有源存在检查器的bean属性。通常这是一种方法的形式hasXYZXYZ是bean映射方法中源bean的属性。当MapStruct 找到这样的方法时,它将调用它hasXYZ而不是执行null检查hasXYZ

可以在MapStruct服务提供程序接口(SPI)中更改源存在检查程序名称。它也可以通过这种方式停用。

某些类型的映射(集合,映射),其中指示MapStruct使用getter或adder作为目标访问器,请参阅CollectionMappingStrategyMapStruct将始终生成源属性null检查,无论其值如何,NullValueheckStrategy以避免添加null到目标集合或地图。

10.9。例外

调用应用程序可能需要在调用映射方法时处理异常。这些异常可以通过手写逻辑以及生成的内置映射方法或MapStruct的类型转换来引发。当调用应用程序需要处理异常时,可以在映射方法中定义throws子句:

例65.使用自定义方法声明已检查异常的映射器
1
2
3
4
5
@Mapper(uses = HandWritten.class)
public interface CarMapper {

    CarDto carToCarDto(Car car) throws GearException;
}

手写逻辑可能如下所示:

例66.自定义映射方法声明已检查的异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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将FatalException一个try-catch包装起来并重新抛出一个未经检查RuntimeExceptionMapStruct委托处理GearException应用程序逻辑,因为它在carToCarDto方法中定义为throws子句

例67.生成的实现中的try-catch块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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;
}

有关空检查的一些注意事项 MapStruct仅在需要时才提供空检查:应用类型转换或通过调用其构造函数构造新类型时。这意味着用户负责手写代码以返回有效的非null对象。此外,null对象可以传递给手写代码,因为MapStruct不希望对用户分配给null对象的含义做出假设。手写代码必须处理这个问题。

11.重用映射配置

本章讨论了为多种映射方法重用映射配置的不同方法:从其他方法“配置”配置并在多个映射器类型之间共享中央配置。

11.1。映射配置继承

方法级配置注解,例如@Mapping@BeanMapping@IterableMapping,等等,都可以继承从一个映射方法的类似使用注释方法@InheritConfiguration

例子68.继承其配置的更新方法
1
2
3
4
5
6
7
8
9
@Mapper
public interface CarMapper {

    @Mapping(target = "numberOfSeats", source = "seatCount")
    Car carDtoToCar(CarDto car);

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

上面的示例声明了一个映射方法carDtoToCar(),该方法具有一个配置,用于定义如何映射numberOfSeats类型中的属性Car在现有实例上执行映射的更新方法Car需要相同的配置才能成功映射所有属性。声明@InheritConfiguration该方法允许MapStruct搜索继承候选者以应用从中继承的方法的注释。

如果所有类型的A(源类型和结果类型)可分配给相应类型的B,则一种方法A可以从另一方法B继承该配置

需要在当前映射器,超类/接口或共享配置接口(如共享配置中所述)中定义被认为是继承的方法

如果多个方法适用于继承的源,则必须在注释中指定方法名称:@InheritConfiguration( name = "carDtoToCar" )

一种方法,可以使用@InheritConfiguration和覆盖或通过另外施加修改的配置@Mapping@BeanMapping等等。

@InheritConfiguration 不能引用已使用的映射器中的方法。

11.2。逆映射

在双向映射的情况下,例如从实体到DTO以及从DTO到实体,前向方法和反向方法的映射规则通常是相似的,并且可以通过切换source简单地反转target

使用注释@InheritInverseConfiguration指示方法应继承相应反向方法的反向配置。

例69.反向映射方法继承其配置并忽略其中的一些
1
2
3
4
5
6
7
8
9
10
@Mapper
public interface CarMapper {

    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToDto(Car car);

    @InheritInverseConfiguration
    @Mapping(target = "numberOfSeats", ignore = true)
    Car carDtoToCar(CarDto carDto);
}

这里的carDtoToCar()方法是反向映射方法carToDto()请注意,任何属性映射carToDto()也将应用于相应的反向映射方法。它们会自动反转并使用@InheritInverseConfiguration注释复制到方法中

从反转方法特定映射可以(可选地)被重写时 ignoreexpressionconstant在映射,例如是这样的:@Mapping(target = "numberOfSeats", ignore=true)

一种方法,被认为是反向的方法的方法,如果结果类型相同的单一源类型的并且如果单一来源型相同的结果类型的

需要在当前映射器(超类/接口)中定义被认为是反向继承的方法。

如果多个方法符合条件,则需要使用如下name属性指定继承配置的方法@InheritInverseConfiguration(name = "carToDto")

@InheritConfiguration在冲突优先于的情况下采取@InheritInverseConfiguration

配置是可传递的。因此,如果方法C定义了一个映射@Mapping( target = "x", ignore = true)B定义了一个映射@Mapping( target = "y", ignore = true),那么如果AB继承继承CA则将继承属性x映射的映射y

表达式和常量被排除(默默忽略)@InheritInverseConfiguration

嵌套源属性的反向映射在1.1.0.Beta2版本中是实验性的。当源属性名称和目标属性名称相同时,将自动进行反向映射。否则,@Mapping应指定目标名称和源名称。在所有情况下,需要采用合适的映射方法来进行反向映射。

@InheritConfiguration或者@InheritInverseConfiguration不能引用已使用的映射器中的方法。

11.3。共享配置

MapStruct提供了通过指向注释的中央接口来定义共享配置的可能性@MapperConfig要使映射器使用共享配置,需要在@Mapper#config属性中定义配置界面

@MapperConfig注释具有相同的属性@Mapper注释。未通过via提供的任何属性@Mapper都将从共享配置继承。指定@Mapper的属性优先于通过引用的配置类指定的属性。列表属性,例如uses简单组合:

例子70. Mapper配置类和使用它的mapper
1
2
3
4
5
6
@MapperConfig(
    uses = CustomMapperViaMapperConfig.class,
    unmappedTargetPolicy = ReportingPolicy.ERROR
)
public interface CentralConfig {
}
1
2
3
4
5
6
7
8
9
@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )
// Effective configuration:
// @Mapper(
//     uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },
//     unmappedTargetPolicy = ReportingPolicy.ERROR
// )
public interface SourceTargetMapper {
  ...
}

保存@MapperConfig注释的接口还可以声明可用于从中继承方法级映射注释的映射方法的原型这些原型方法并不意味着要作为映射器API的一部分来实现或使用。

例71.使用原型方法的Mapper配置类
1
2
3
4
5
6
7
8
9
10
11
@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);
}
1
2
3
4
5
6
7
8
@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)
}

当方法级映射配置注释从接口中的原型方法继承到映射器中的方法时,属性@Mapper#mappingInheritanceStrategy()/ @MapperConfig#mappingInheritanceStrategy()配置:

  • EXPLICIT(默认):只有目标映射方法注释时才会继承配置,@InheritConfiguration并且源和目标类型可分配给原型方法的相应类型,所有这些都在映射配置继承中描述

  • AUTO_INHERIT_FROM_CONFIG:如果目标映射方法的源和目标类型可分配给原型方法的相应类型,则配置将自动继承。如果多个原型方法匹配,则必须解决歧义@InheritConfiguration(name = …​),这将导致AUTO_INHERIT_FROM_CONFIG被忽略。

  • AUTO_INHERIT_REVERSE_FROM_CONFIG:如果目标映射方法的源和目标类型可分配给原型方法的相应类型,则将自动继承反向配置。如果多个原型方法匹配,则必须解决歧义@InheritInverseConfiguration(name = …​),这将导致`AUTO_INHERIT_REVERSE_FROM_CONFIG被忽略。

  • AUTO_INHERIT_ALL_FROM_CONFIG:配置和反向配置都将自动继承。同样的规则适用于AUTO_INHERIT_FROM_CONFIGAUTO_INHERIT_REVERSE_FROM_CONFIG

12.自定义映射

有时需要在某些映射方法之前或之后应用自定义逻辑。MapStruct提供了两种方法:装饰器允许特定映射方法的类型安全定制,以及映射前和映射后生命周期方法,允许对给定源或目标类型的映射方法进行通用定制。

12.1。使用装饰器映射自定义

在某些情况下,可能需要定制生成的映射方法,例如,在目标对象中设置不能由生成的方法实现设置的附加属性。MapStruct使用装饰器支持此要求。

使用组件模型时cdi,请使用带有MapStruct映射器的CDI装饰器,而不是@DecoratedWith此处描述注释。

要将装饰器应用于映射器类,请使用@DecoratedWith注释指定它

例72.应用装饰器
1
2
3
4
5
6
7
8
9
10
@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {

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

    PersonDto personToPersonDto(Person person);

    AddressDto addressToAddressDto(Address address);
}

装饰器必须是装饰映射器类型的子类型。您可以将其设置为一个抽象类,它只允许实现您要自定义的映射器接口的那些方法。对于所有未实现的方法,将使用默认生成例程生成对原始映射器的简单委派。

PersonMapperDecorator下面示出定制personToPersonDto()它设置了一个附加属性,该属性在映射的源类型中不存在。addressToAddressDto()方法不是自定义的。

例子73.实现装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class PersonMapperDecorator implements PersonMapper {

    private final PersonMapper delegate;

    public PersonMapperDecorator(PersonMapper delegate) {
        this.delegate = delegate;
    }

    @Override
    public PersonDto personToPersonDto(Person person) {
        PersonDto dto = delegate.personToPersonDto( person );
        dto.setFullName( person.getFirstName() + " " + person.getLastName() );
        return dto;
    }
}

该示例显示了如何选择使用生成的默认实现注入委托,并在自定义装饰器方法中使用此委托。

对于映射器componentModel = "default",使用单个参数定义构造函数,该参数接受装饰映射器的类型。

当与组件模型的工作spring或者jsr330,这需要不同的处理。

12.1.1。具有Spring组件模型的装饰器

@DecoratedWith具有组件模型spring的映射器上使用时,使用Spring注释对生成的原始映射器实现进行注释@Qualifier("delegate")要在装饰器中自动装配该bean,还要添加该限定符注释:

例74.基于Spring的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
     }
 }

扩展装饰器的生成类使用Spring的@Primary注释进行注释。要在应用程序中自动装配已装饰的映射器,不需要做任何特殊操作:

例子75.使用装饰的映射器
1
2
@Autowired
private PersonMapper personMapper; // injects the decorator, with the injected original mapper

12.1.2。具有JSR 330组件模型的装饰器

JSR 330没有指定限定符,只允许特定地命名bean。因此,生成的原始映射器的实现被注释@Named("fully-qualified-name-of-generated-implementation")(请注意,当使用装饰器时,映射器实现的类名以下划线结尾)。要在装饰器中注入该bean,请将相同的注释添加到委托字段(例如,通过从生成的类复制/粘贴它):

例76.基于JSR 330的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
    }
}

与其他组件模型不同,使用站点必须知道是否装饰了映射器,对于装饰映射器,@Named必须添加无参数注释以选择要注入的装饰器:

例77.使用带有JSR 330的装饰映射器
1
2
3
@Inject
@Named
private PersonMapper personMapper; // injects the decorator, with the injected original mapper

@DecoratedWith结合组件模型jsr330被认为是1.0.0.CR2版本的实验。在装饰器中引用原始映射器的方式或者在应用程序代码中注入装饰映射器的方式可能仍会改变。

12.2。使用before-mapping和after-mapping方法映射自定义

在定制映射器时,装饰器可能并不总是满足需要。例如,如果您不仅需要为少数选定的方法执行自定义,而且还需要为映射特定超类型的所有方法执行自定义:在这种情况下,您可以使用在映射开始之前或映射完成之后调用的回调方法

回调方法可以在抽象映射器本身,类型引用中Mapper#uses或用作@Context参数的类型中实现

例78.使用@BeforeMapping和@AfterMapping挂钩的Mapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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;
    }
}

如果@BeforeMapping/ @AfterMapping方法具有参数,则仅在方法的返回类型(如果为非void)可分配给映射方法的返回类型且所有参数可由映射的源或目标参数指定时才会生成方法调用。方法:

  • 注释的参数@MappingTarget将填充映射的目标实例。

  • 注释的参数@TargetType将填充映射的目标类型。

  • 注释@Context的参数使用映射方法的上下文参数填充。

  • 使用映射的source参数填充任何其他参数。

对于非void方法,如果不是,则返回方法调用的返回值作为映射方法的结果null

与映射方法一样,可以为before / after-mapping方法指定类型参数。

例79.使用@AfterMapping挂钩的Mapper返回非空值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@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;
    }
}

所有之前/之后映射方法被应用到的映射方法被使用。基于限定符的映射方法选择可用于进一步控制可以选择哪些方法而不选择哪些方法。为此,限定符注释需要应用于before / after-method并在BeanMapping#qualifiedByor中引用IterableMapping#qualifiedBy

方法调用的顺序主要取决于它们的变体:

  1. @BeforeMapping@MappingTarget在对源参数进行任何空值检查并构造新的目标bean之前,将调用没有参数的方法

  2. @BeforeMapping@MappingTarget在构造新的目标bean之后调用带有参数的方法

  3. @AfterMapping在最后一个return语句之前的映射方法的末尾调用方法

在这些组中,方法调用按其定义位置排序:

  1. @Context参数上声明的方法,按参数顺序排序。

  2. 映射器本身实现的方法。

  3. Mapper#uses()注释中的类型声明的顺序引用的类型中的方法。

  4. 在一个类型中声明的方法在超类型声明的方法之后使用。

重要提示:无法保证在一种类型中声明的方法顺序,因为它取决于编译器和处理环境实现。

13.使用MapStruct SPI

13.1。自定义访问者命名策略

MapStruct提供了AccessorNamingStrategy通过服务提供接口(SPI)覆盖的可能性一个很好的例子是在源对象GolfPlayer及其GolfPlayerDto下方使用流畅的API

示例80.具有流畅API的源对象GolfPlayer。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
    }
}
例81.源对象GolfPlayerDto,具有流畅的API。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
    }
}

我们希望GolfPlayer映射到GolfPlayerDto类似于我们总是这样做的目标对象

例82.具有流畅API的源对象。
1
2
3
4
5
6
7
8
9
10
@Mapper
public interface GolfPlayerMapper {

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

    GolfPlayerDto toDto(GolfPlayer player);

    GolfPlayer toPlayer(GolfPlayerDto player);

}

这可以通过实现SPI来实现,org.mapstruct.ap.spi.AccessorNamingStrategy如以下示例所示。这是一个实施的org.mapstruct.ap.spi.AccessorNamingStrategy

例子83. CustomAccessorNamingStrategy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * 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 );
    }
}

CustomAccessorNamingStrategy品牌使用的DefaultAccessorNamingStrategy(在mapstruct处理器也可),并依赖于一流的离开最不变的默认行为。

要使用自定义SPI实现,它必须与META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy具有自定义实现的完全限定名称的文件一起位于单独的JAR文件中作为内容(例如org.mapstruct.example.CustomAccessorNamingStrategy)。需要将此JAR文件添加到注释处理器类路径(即将其添加到添加了mapstruct-processor jar的位置旁边)。

更多详细信息:上面的示例存在于我们的示例存储库(https://github.com/mapstruct/mapstruct-examples)中。

13.2。映射排除提供程序

MapStruct提供了MappingExclusionProvider通过服务提供程序接口(SPI)覆盖的可能性一个很好的例子是不允许MapStruct为某种类型创建自动子映射,即MapStruct不会尝试为排除类型生成自动子映射方法。

DefaultMappingExclusionProvider会排除在所有类型javajavax包。这意味着MapStruct不会尝试在某个自定义类型和Java类库中声明的某些类型之间生成自动子映射方法。

例子84.源对象
1
2
3
4
5
6
7
8
9
10
public class Source {

    static class NestedSource {
        private String property;
        // getters and setters
    }

    private NestedSource nested;
    // getters and setters
}
例子85.目标对象
1
2
3
4
5
6
7
8
9
10
public class Target {

    static class NestedTarget {
        private String property;
        // getters and setters
    }

    private NestedTarget nested;
    // getters and setters
}
例子86.映射器定义
1
2
3
4
5
@Mapper
public interface ErroneousCustomExclusionMapper {

    Target map(Source source);
}

我们想要NestedTarget从自动子映射方法生成中排除

例87. CustomMappingExclusionProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.regex.Pattern;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;

import org.mapstruct.ap.spi.MappingExclusionProvider;

public class CustomMappingExclusionProvider implements MappingExclusionProvider {
    private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" );

    @Override
    public boolean isExcluded(TypeElement typeElement) {
        Name name = typeElement.getQualifiedName();
        return name.length() != 0 && ( JAVA_JAVAX_PACKAGE.matcher( name ).matches() ||
            name.toString().equals( "org.mapstruct.ap.test.nestedbeans.exclusions.custom.Target.NestedTarget" ) );
    }
}

要使用自定义SPI实现,它必须与META-INF/services/org.mapstruct.ap.spi.MappingExclusionProvider具有自定义实现的完全限定名称的文件一起位于单独的JAR文件中作为内容(例如org.mapstruct.example.CustomMappingExclusionProvider)。需要将此JAR文件添加到注释处理器类路径(即将其添加到添加了mapstruct-processor jar的位置旁边)。

13.3。Custom Builder Provider

MapStruct提供了DefaultProvider通过服务提供程序接口(SPI)覆盖的可能性一个很好的例子是为自定义构建器策略提供支持。

示例88.禁用Builder支持的Custom Builder Provider
1
2
3
4
5
6
7
8
9
10
import javax.lang.model.type.TypeMirror;

public class NoOpBuilderProvider implements BuilderProvider {

    @Override
    public BuilderInfo findBuilderInfo(TypeMirror type) {
        return null;
    }
}

原文