在前面, 介绍了 MapStruct 及其入门。 本文则是进一步的进阶。

MapStruct 生成对应的实现类的时候, 有如下的几个情景。

1 属性名称相同,则进行转化

在实现类的时候, 如果属性名称相同, 则会进行对应的转化。这个在之前的文章代码中已经有所体现。 通过此种方式, 我们可以快速的编写出转换的方法。

源对象类

  1. import lombok.Data;
  2. @Data
  3. public class Source {
  4. private String id;
  5. private Integer num;
  6. }

目标对象类

  1. import lombok.Data;
  2. @Data
  3. public class Target {
  4. private String id;
  5. private Integer num;
  6. }

转化类


  1. @Mapper
  2. public interface SourceMapper {
  3. SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
  4. Target source2target(Source source);
  5. }

由于 SourceTarget 需要转化的属性是完全相同的。因此, 在 Mapper 中, source2target 方法很快就可以编写出来了。 只需要确定入参和返回值即可。

2 属性名不相同, 可通过 @Mapping 注解进行指定转化。

属性名不相同, 在需要进行互相转化的时候, 则我们可以通过 @Mapping 注解来进行转化。

在上面的 Source 类中, 增加一个属性 totalCount

  1. @Data
  2. public class Source {
  3. private String id;
  4. private Integer num;
  5. private Integer totalCount;
  6. }

而对应的 Target 中, 定义的属性是 count

  1. @Data
  2. public class Target {
  3. private String id;
  4. private Integer num;
  5. private Integer count;
  6. }

如果方法没做任何的改变, 那么,在转化的时候, 由于属性名称不相同, 会导致 count 属性没有值。

这时候, 可以通过 @Mappimg 的方式进行映射。

  1. @Mapper
  2. public interface SourceMapper {
  3. SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
  4. @Mapping(source = "totalCount", target = "count")
  5. Target source2target(Source source);
  6. }

仅仅是在方法上面加了一行。再次允许测试程序。

3 Mapper 中使用自定义的转换

有时候, 对于某些类型, 无法通过代码生成器的形式来进行处理。 那么, 就需要自定义的方法来进行转换。 这时候, 我们可以在接口(同一个接口, 后续还有调用别的 Mapper 的方法)中定义默认方法(Java8及之后)。

Source 类中增加

  1. private SubSource subSource;

对应的类


  1. import lombok.Data;
  2. @Data
  3. public class SubSource {
  4. private Integer deleted;
  5. private String name;
  6. }

相应的, 在 Target

  1. private SubTarget subTarget;

对应的类

  1. import lombok.Data;
  2. @Data
  3. public class SubTarget {
  4. private Boolean result;
  5. private String name;
  6. }

然后在 SourceMapper 中添加方法及映射, 对应的方法更改后

  1. @Mapper
  2. public interface SourceMapper {
  3. SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
  4. @Mapping(source = "totalCount", target = "count")
  5. @Mapping(source = "subSource", target = "subTarget")
  6. Target source2target(Source source);
  7. default SubTarget subSource2subTarget(SubSource subSource) {
  8. if (subSource == null) {
  9. return null;
  10. }
  11. SubTarget subTarget = new SubTarget();
  12. subTarget.setResult(!subSource.getDeleted().equals(0));
  13. subTarget.setName(subSource.getName()==null?"":subSource.getName()+subSource.getName());
  14. return subTarget;
  15. }
  16. }

进行测试

4 多转一

我们在实际的业务中少不了将多个对象转换成一个的场景。 MapStruct 当然也支持多转一的操作。

AddressPerson 两个对象。

  1. import lombok.Data;
  2. @Data
  3. public class Address {
  4. private String street;
  5. private int zipCode;
  6. private int houseNo;
  7. private String description;
  8. }
  9. @Data
  10. public class Person {
  11. private String firstName;
  12. private String lastName;
  13. private int height;
  14. private String description;
  15. }

而在实际的使用时, 我们需要的是 DeliveryAddress

  1. import lombok.Data;
  2. @Data
  3. public class DeliveryAddress {
  4. private String firstName;
  5. private String lastName;
  6. private int height;
  7. private String street;
  8. private int zipCode;
  9. private int houseNumber;
  10. private String description;
  11. }

其对应的信息不仅仅来自一个类, 那么, 我们也可以通过配置来实现多到一的转换。

  1. @Mapper
  2. public interface AddressMapper {
  3. AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
  4. @Mapping(source = "person.description", target = "description")
  5. @Mapping(source = "address.houseNo", target = "houseNumber")
  6. DeliveryAddress personAndAddressToDeliveryAddressDto(Person person, Address address);
  7. }

测试

在多对一转换时, 遵循以下几个原则

  1. 当多个对象中, 有其中一个为 null, 则会直接返回 null
  2. 如一对一转换一样, 属性通过名字来自动匹配。 因此, 名称和类型相同的不需要进行特殊处理
  3. 当多个原对象中,有相同名字的属性时,需要通过 @Mapping 注解来具体的指定, 以免出现歧义(不指定会报错)。 如上面的 description

属性也可以直接从传入的参数来赋值。

  1. @Mapping(source = "person.description", target = "description")
  2. @Mapping(source = "hn", target = "houseNumber")
  3. DeliveryAddress personAndAddressToDeliveryAddressDto(Person person, Integer hn);

在上面的例子中, hn 直接赋值给 houseNumber

5 更新 Bean 对象

有时候, 我们不是想返回一个新的 Bean 对象, 而是希望更新传入对象的一些属性。这个在实际的时候也会经常使用到。

AddressMapper 类中, 新增如下方法

  1. /**
  2. * Person->DeliveryAddress, 缺失地址信息
  3. * @param person
  4. * @return
  5. */
  6. DeliveryAddress person2deliveryAddress(Person person);
  7. /**
  8. * 更新, 使用 Address 来补全 DeliveryAddress 信息。 注意注解 @MappingTarget
  9. * @param address
  10. * @param deliveryAddress
  11. */
  12. void updateDeliveryAddressFromAddress(Address address,
  13. @MappingTarget DeliveryAddress deliveryAddress);

注解 @MappingTarget后面跟的对象会被更新。 以上的代码可以通过以下的测试。

  1. @Test
  2. public void updateDeliveryAddressFromAddress() {
  3. Person person = new Person();
  4. person.setFirstName("first");
  5. person.setDescription("perSonDescription");
  6. person.setHeight(183);
  7. person.setLastName("homejim");
  8. DeliveryAddress deliveryAddress = AddressMapper.INSTANCE.person2deliveryAddress(person);
  9. assertEquals(deliveryAddress.getFirstName(), person.getFirstName());
  10. assertNull(deliveryAddress.getStreet());
  11. Address address = new Address();
  12. address.setDescription("addressDescription");
  13. address.setHouseNo(29);
  14. address.setStreet("street");
  15. address.setZipCode(344);
  16. AddressMapper.INSTANCE.updateDeliveryAddressFromAddress(address, deliveryAddress);
  17. assertNotNull(deliveryAddress.getStreet());
  18. }

6 获取 mapper

6.1 通过 Mapper 工厂获取

在上面的例子中, 我们都是通过 Mappers.getMapper(xxx.class) 的方式来进行对应 Mapper 的获取。 此种方法为通过 Mapper 工厂获取。

如果是此种方法, 约定俗成的是在接口内定义一个接口本身的实例 INSTANCE, 以方便获取对应的实例。

  1. @Mapper
  2. public interface SourceMapper {
  3. SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
  4. // ......
  5. }

这样在调用的时候, 我们就不需要在重复的去实例化对象了。类似下面

  1. Target target = SourceMapper.INSTANCE.source2target(source);

6.2 使用依赖注入

对于 Web 开发, 依赖注入应该很熟悉。 MapSturct 也支持使用依赖注入, 同时也推荐使用依赖注入。

注入方式
default 默认的方式, 使用 Mappers.getMapper(Class) 来进行获取 Mapper
cdi Contexts and Dependency Injection. 使用此种方式, 需要使用 @Inject 来进行注入
spring Spring 的方式, 可以通过 @Autowired 来进行注入
jsr330 生成的 Mapper 中, 使用 @javax.inject.Named@Singleton 注解, 通过 @Inject 来注入

6.3 依赖注入策略

可以选择是通过构造方法或者属性注入, 默认是属性注入。

  1. public enum InjectionStrategy {
  2. /** Annotations are written on the field **/
  3. FIELD,
  4. /** Annotations are written on the constructor **/
  5. CONSTRUCTOR
  6. }

类似如此使用

  1. @Mapper(componentModel = "cdi", uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)

优雅的对象转换解决方案-MapStruct使用进阶(二)的更多相关文章

  1. 优雅的对象转换解决方案-MapStruct及其入门(一)

    第一次看到 MapStruct 的时候, 我个人非常的开心. 因为其跟我内心里面的想法不谋而合. 1 MapStruct 是什么? 1.1 JavaBean 的困扰 对于代码中 JavaBean之间的 ...

  2. 对象转换工具 MapStruct 介绍

    前言 在我们日常开发的分层结构的应用程序中,为了各层之间互相解耦,一般都会定义不同的对象用来在不同层之间传递数据,因此,就有了各种 XXXDTO.XXXVO.XXXBO 等基于数据库对象派生出来的对象 ...

  3. Fiddler对安卓高版本进行抓包解决方案以及分析 进阶二

    今天是2021年的最后一天了,多分享一些干货吧!看过上一章节教程后会有同学疑惑,我也一步一个脚印的,跟着流程走也设置了代理以及安装了证书,有的同学会发现 为什么手机不能够连接网络了呢?细心一点的同学会 ...

  4. 对象拷贝 - 优雅的解决方案 Mapstruct

    MapStruct GitHub 访问地址 : https://github.com/mapstruct/mapstruct/ 使用例子 : https://github.com/mapstruct/ ...

  5. mapstruct解放Java对象转换

    摘要 当前web后端开发,都是使用多层工程结构,需要在VO,BO,DTO,DO等各种数据结构中相互转换.这些转换代码都是些比较简单的字段映射,类型转换,重复性工作比较高,可以使用一些工具解放我们的双手 ...

  6. 对象转换利器之Dozer

    什么是Dozer Dozer是一个Java对象转换工具,可以在JavaBean和JavaBean之间进行递归数据复制,并且适应不同复杂的类型.Dozer会直接将名称相同的属性进行复制,属性名不同或者有 ...

  7. 手机端页面自适应解决方案—rem布局进阶版

    手机端页面自适应解决方案—rem布局进阶版   https://www.jianshu.com/p/985d26b40199 注:本文转载之处:https://www.cnblogs.com/anni ...

  8. java对象转换

    对象转换: 对象的分层涉及到各个层级之间的对象转换(Entity2DTO , DTO2VO, VO2DTO,DTO2Entity等),传统的采用set/get 方法硬编码实现写的代码比较多:或者采用B ...

  9. 我写了个IDEA开源插件,vo2dto 一键生成对象转换

    让人头疼的对象转换 头炸,po2vo.vo2do.do2dto,一堆对象属性,取出来塞进来.要不是为了 DDD 架构下的各个分层防腐,真想一竿子怼下去. 那上 BeanUtils.copyProper ...

随机推荐

  1. AD域控制器安装使用

    AD域控制器安装使用 一. 在服务器上安装域控制器 二. 将此服务器提升为域控制器 三. 将主机加入到我们创建的域中 在AD域控制器上查看加入的主机

  2. 彻底弄懂UTF-8、Unicode、宽字符、locale

    目录 Unicode.UCS UTF8 宽字符类型wchar_t locale 为什么需要宽字符类型 多字节字符串和宽字符串相互转换 最近使用到了wchar_t类型,所以准备详细探究下,没想到水还挺深 ...

  3. STM32学习的一些实例

    第一讲:修炼STM32之乾坤大挪移术—— 如何用DMA神器搬运数据DMA,即直接存储器访问.DMA 传输方式无需 CPU 直接控制传输,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, ...

  4. 03_javaSE面试题:类初始化和实例初始化

    题目 下面代码运行的结果是什么? Father 类 /** * @author kevin * @date 2019/7/8 15:48 */ public class Father { privat ...

  5. 记一次SQL优化。

    程序是数据库的用户,为打造良好的用户体验,我们一直在努力. 此次介绍一个基于SQL的数据库优化.SQL的优劣对数据库的性能影响非常关键. 查询只涉及如下表结构中的三个字段.如下 开发原始SQL SEL ...

  6. MacBook强制清除gardle缓存

    背景:在日常的工作开发中,为了方便维护一般采用gardle+Nexus的模式管理jar包,但方便的同时也会存在一些问题 例如:test-1.0.3.jar  jar包提供方修改了一些问题上传到Nexu ...

  7. springboot2.0.4对接redis3.2.12版本哨兵模式

    redis 哨兵模式的创建 1. 下载redis3.2.12版本.https://codeload.github.com/antirez/redis/zip/3.2.12 2.  解压后放到/usr/ ...

  8. 一个内存不能被written的问题

    C程序面试中曾经面试过这样一道题: #include <stdio.h> int main() { char *p = "12345"; *p = '6'; print ...

  9. [记录]NGINX配置HTTPS性能优化方案一则

    NGINX配置HTTPS性能优化方案一则: 1)HSTS的合理使用 2)会话恢复的合理使用 3)Ocsp stapling的合理使用 4)TLS协议的合理配置 5)False Start的合理使用 6 ...

  10. c++小游戏——杀手

    杀手小游戏 会有一个存活者:(1 2 3 4 5),如果出现(1 0 3 4 5),代表二号已经死了. 一号有3次复活权 且有一次随机诅咒权(即当自己被杀死时,会随机诅咒另外一个人,当然不是死人或自己 ...