MapStruct增强设计

362 阅读5分钟

一. 背景

由于每个人对对象映射的使用工具不一致,导致各个应用存在对象映射工具使用混杂多样,没有一个统一对象映射工具去规范使用,各个对象映射的工具性能也参差不齐;有的通过反射实现,比较损耗性能;有的会进行类型自动转换,容易导致数据问题,针对以上的一些问题,对对象映射框架进行一次调研。

二. 对象映射框架性能对比

工具实现方式star最近更新时间说明
Getter/Settergetter/setter方法属性很多时不好维护
Mapstructgetter/setter方法6k2023-02-05基于JSR269,在在编译期生成对象映射代码
Orika动态生成字节码1.2k2022-07-13基于javassist生成对象映射字节码,并加载生成的字节码文件
Spring BeanUtils反射机制基于Spring反射工具类
BeanCopier基于ASM的MethodVisitor为field赋值4.6k2022-02-08使用ASM的MethodVisitor直接编写各属性的get/set方法
Dozer反射机制2k2023-01-06大量反射,主要基于Field.set(obj, obj)为field赋值
Apache BeanUtils反射机制2362023-02-25
ModelMapper反射机制2.1k2022-12-08

1、压测

机器处理器:M1 Pro (10 核中央处理器和16 核图形处理器)

机器内存:32G

平均耗时/吞吐量(JMH基准测试预热3次,每次3秒,真正执行5次,每次3次)

工具5个线程拷贝1w次5个线程拷贝10w次5个线程拷贝100w次5个线程拷贝500w次排名
吞吐量(ops/s)平均耗时(ms)吞吐量(ops/s)平均耗时(ms)吞吐量(ops/s)平均耗时(ms)吞吐量(ops/s)平均耗时(ms)
Mapstruct226.243427.801352.4813870.28941681
Orika21.139454.7142060.6951423--4
Spring BeanUtils125.025715.377631.2347700.14767453
BeanCopier188.295522.215461.9565230.21443322
Dozer22.829462.2254570.2044872--6
Apache BeanUtils29.679333.02029511----7
ModelMapper21.575462.2794340.2084897--5

综上比较可知性能比较好的对象映射框架为MapStruct和cglib下的BeanCopier,但BeanCopier只拷贝名称和类型都相同的属性;即便基本类型与其对应的包装类型也不能相互转换, 而且BeanCopier社区活跃度没有MapStruct好,最近更新时间也22年2月8号,几乎不怎么更新了,所以最佳的选择是MapStruct

三. MapStruct用法

1、pom.xml

<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>1.5.2.Final</version>
  <scope>compile</scope>
</dependency>
<dependency>
 <groupId>org.mapstruct</groupId>
 <artifactId>mapstruct-processor</artifactId>
 <version>1.5.2.Final</version>
</dependency>

2、定义Mapper

@Mapper(componentModel="spring")
public interface SourceToTargetMapper {
    SourceToTargetMapper INSTANCE = Mappers.getMapper(SourceToTargetMapper.class);

    Target to(Source source);
}

3、使用

Target target=SourceToTargetMapper.INSTANCE.to(buildSource());

四. MapStruct问题

1、手动编写mapper太麻烦
2、mapper太多记不住,使用不够方便,没Util来的方便

五. MapStruct原理

APT:APT(编译时注解处理器)即为Annotation Processing Tool,是JSR269规范提供的一套API。APT可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。APT的核心是AbstractProcessor类

图片

六. MapStruct增强方案

技术栈:APT+JavaPoet+SPI

JavaPoet:JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件。

1、手动编写mapper太麻烦

解决方案:编译阶段自动生成Mapper,减少手动编写Mapper

图片

2、mapper太多记不住,使用不够方便

解决方案:封装MapStructUtils代替Mapper调用,使用更方便,但有一定的性能损耗

图片

七. MapStruct增强后使用以及性能压测

1、使用

引入pom

<dependency>
    <groupId>com.xxx.xxx</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

DTO上定义注解

@Data
@Builder
@AutoMap(targets = {Target.class},componentModel = "spring")
public class Source {
    private Integer id;

    private String name;
}
@Data
@Builder
@AutoMap(targets = {Source.class})
public class Target {
    private Integer id;

    private String name;

    private Integer shorName;
}

使用:
方式一(推荐)
Target target= SourceTargetMapper.INSTANCE.convert(buildSource());

方式二

Target target= MapStructUtils.convert(buildSource(), Target.class);

2、性能压测

工具5个线程拷贝1w次5个线程拷贝10w次5个线程拷贝100w次5个线程拷贝500w次排名
吞吐量(ops/s)平均耗时(ms)吞吐量(ops/s)平均耗时(ms)吞吐量(ops/s)平均耗时(ms)吞吐量(ops/s)平均耗时(ms)
Mapstruct226.243427.801352.4813870.28941681
BeanCopier188.295522.215461.9565230.21443322
MapStructUtil110.439**7 **13.536761.250**837 ****0.150 **6669

八. MapStruct增强后优缺点

优点:

1、无需再定义Mapper,编译阶段自动生成

2、封装了Util,使用简单

3、增强后依旧在编译阶段运行,不影响原生MapStruct的功能使用,不会影响程序性能

4、解决了Mapstruct原生默认不支持lombok的@Builder注解导致父类不生成set方法的问题

缺点:

1、封装的Util比原生的Mapper性能差一点,但比其他对象拷贝框架(非MapStruct)性能还是强很多

2、若开发人员手动在dto目标新建apt目录,且新增跟生成的Mapper名称一样的文件的话会不兼容,概率较低

3、增强后能解决90%的使用场景,但一些自定义的注解在自动生成的Mapper里未集成,比如不同名称字段映射等

 参考资料

原文链接:mp.weixin.qq.com/s?__biz=MzU…