1. 概述
在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,DO 一般不会让外部依赖,这时候需要在提供对外接口的模块里放 DTO 用于对象传输,也即是 DO 对象对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。
这种 对象与对象之间的互相转换,就需要有一个专门用来解决转换问题的工具,毕竟每一个字段都 get/set 会很麻烦。
MapStruct 就是这样的一个属性映射工具,只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。MapStruct官网地址: mapstruct.org。
2. Maven
让我们在Maven pom.xml中添加以下依赖项:
1 | <dependency> |
最新的Mapstruct及其processor稳定版本均可从Maven Central Repository获得。
我们还将annotationProcessorPaths部分添加到maven-compiler-plugin插件的配置部分,用于生成具体的映射器实现:
1 | <plugin> |
3. 基本映射
3.1 创建基本的 POJO
让我们首先创建一个简单的Java POJO:
1 | public class SimpleSource { |
3.2. 映射器接口
1 |
|
请注意,我们没有为SimpleSourceDestinationMapper创建实现类- 因为MapStruct将为我们创建它。
3.3. 映射器接口实现
我们可以通过执行mvn clean install来触发MapStruct处理,插件将在 /target/generated-sources/annotations/下生成具体的实现类,如下所示:
1 | public class SimpleSourceDestinationMapperImpl |
3.4 测试用例
最后,通过生成所有内容,让我们编写一个测试用例,将显示SimpleSource中的值与SimpleDestination中的值匹配:
1 | public class SimpleSourceDestinationMapperTest { |
4. 使用 Spring 依赖注入
在上面的单元测试中,我们通过调用Mappers.getMapper(YourClass.class)来获取MapStruct中mapper的实例。当然,这是获取实例的一种非常手动的方式 - 更好的替代方法是将mapper直接注入我们需要的位置(如果我们的项目使用任何依赖注入解决方案)。
幸运的是,MapStruct对Spring和CDI(上下文和依赖注入)都提供了很好的支持。
要在我们的映射器中使用Spring IoC,只需要给@Mapper注解添加 componentModel=spring 属性,如下所示:
1 | "spring") (componentModel = |
5. 使用不同字段名称映射字段
从前面的示例中,MapStruct能够自动映射我们的bean,因为它们具有相同的字段名称。那么如果我们要映射的bean有不同的字段名称呢?
对于我们的示例,我们将创建一个名为Employee和EmployeeDTO的新bean 。
5.1 新的 POJO
1 | public class EmployeeDTO { |
1 | public class Employee { |
5.2 Mapper
在映射不同的字段名称时,我们需要将其源字段配置为其目标字段,为此,我们需要添加@Mappings注释。此批注接受一个@Mapping批注数组,我们将使用它来添加目标和源属性。
在MapStruct中,我们还可以使用点表示法来定义bean的成员:
1 |
|
5.3 测试用例
我们再次需要测试源和目标对象值是否匹配:
1 |
|
6. 嵌套映射
接下来,我们将展示如何使用对其他bean的引用来映射bean。
6.1 修改 POJO
让我们为Employee对象添加一个新的bean引用:
1 | public class EmployeeDTO { |
1 | public class Employee { |
1 | public class Division { |
6.2 修改Mapper
这里我们需要添加一个方法将Division转换为DivisionDTO,反之亦然; 如果MapStruct检测到需要转换对象类型并且转换方法存在于同一个类中,那么它将自动使用它。
让我们将它添加到映射器:
1 | DivisionDTO divisionToDivisionDTO(Division entity); |
6.3 修改测试用例
让我们修改并添加一些测试用例到现有测试用例:
1 |
|
7. 映射过程中的类型转换
MapStruct还提供了几个现成的隐式类型转换,对于我们的示例,我们将尝试将String日期转换为实际的Date对象。
有关隐式类型转换的更多详细信息,可以阅读MapStruct参考指南。
7.1 修改 POJO
1 | public class Employee { |
1 | public class EmployeeDTO { |
7.2 修改Mapper
修改映射器并为我们的开始日期提供dateFormat:
1 | ({ |
7.3 修改测试用例
让我们再添加一些测试用例来验证转换是否正确:
1 | private static final String DATE_FORMAT = "dd-MM-yyyy HH:mm:ss"; |
8. 自定义映射结合(抽象类)
有时,我们希望通过自定义一些映射方式来扩展 @Mapping功能,比如除了类型转换之外,我们可能希望以某种方式转换值,如下面的示例所示,在这种情况下,我们可以创建一个抽象类并实现我们想要自定义的方法,并留下那些应该由MapStruct生成的抽象类。
8. 1 基本模型
在这个例子中,我们将使用以下类:
1 | public class Transaction { |
匹配的 DTO:
1 | public class TransactionDTO { |
这里棘手的部分是将BigDecimal 总金额转换为Long totalInCents。
8.2 定义 Mapper
我们可以通过将Mapper 创建 为抽象类来实现:
1 |
|
在这里,我们为单个对象转换实现了完全自定义的映射方法。
另一方面,我们留下了将Collection映射到List抽象的方法,因此MapStruct 将为我们实现它。
8.3 生成的结果
由于我们已经实现了将单个Transaction映射到TransactionDTO的方法,我们希望Mapstruct在第二种方法中使用它。将生成以下内容:
1 |
|
正如我们在第12行中看到的,MapStruct 在它生成的方法中使用我们的实现。
9. 支持 Lombok
在最新版本的MapStruct中,已经提供了对Lombok的支持。因此,我们可以使用Lombok轻松映射源实体和目标。 要启用Lombok支持,我们需要在注释处理器路径中添加依赖项。所以现在我们在Maven编译器插件中有了mapstruct-processor和Lombok:
1 | <plugin> |
让我们使用Lombok注释定义源实体:
1 |
|
和目标数据传输对象:
1 |
|
这个mapper接口仍然类似于我们前面的例子:
1 |
|
10. 支持 defaultExpression
从版本1.3.0开始,我们可以使用@Mapping批注的defaultExpression属性来指定一个表达式,如果源字段为null,则该表达式确定目标字段的值。这是现有defaultValue属性功能的补充。
源实体:
1 | public class Person { |
目标数据传输对象:
1 | public class PersonDTO { |
如果源实体的id字段为null,我们想要生成一个随机id并将其分配给目标,保持其他属性值为:
1 |
|
让我们添加一个测试用例来验证表达式的执行:
1 |
|