MapStruct
1、MapStruct简介
- 它是什么?
MapStruct 是一个代码生成器,它基于约定优于配置的方法,极大地简化了 Java bean 类型之间的映射实现。生成的映射代码使用普通的方法调用,因此速度快、类型安全且易于理解。
- 为什么?
多层应用程序通常需要在不同的对象模型(例如实体和 DTO)之间进行映射。编写这样的映射代码是一项繁琐且容易出错的任务。MapStruct 旨在通过尽可能地自动化来简化这项工作。与其他映射框架相比,MapStruct 在编译时生成 bean 映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。
- 如何?
MapStruct 是一个注解处理器,它插入到 Java 编译器中,可用于命令行构建(Maven、Gradle 等)以及您首选的 IDE。MapStruct 使用合理的默认值,但在配置或实现特殊行为时会采取措施。
官方参考文档:https://mapstruct.org/documentation/stable/reference/html/
2、原理分析
常见的属性拷贝工具:
工具 | 来源 | 原理 | 备注 | 10个字段200万次耗时(ms) | 特点 |
---|---|---|---|---|---|
BeanUtils | apache | 运行期通过java反射机制动态映射 | (阿里代码规范不推荐)apache追求严谨,校验、日志很多 | / | 仅支持属性名称与类型相同的赋值 |
BeanUtils | spring | 运行期通过java反射机制动态映射 | 相比apache减少了校验与日志 | 1774 | |
BeanCopier | cglib | 动态字节码 | 675 | 支持属性名称与类型相同的赋值,特殊赋值 | |
MapStruct | mapstruct | 编译期动态生成getter/setter | 类似lombook 编译期间生成代码 | 669 | 支持字段不同类型赋值,特殊赋值 |
- 通过反射
直接调用的时候,是静态的 ,实例类型,方法名,参数类型这些都是明确的,编译阶段已经处理了权限,方法可见性,参数类型等校验,之后jvm加载解析的时候已经将方法的符号引用转为地址引用了,到我们执行方法的时候,就可以直接新建栈帧进行方法调用了。
那么反射的时候就不一样了,反射是动态的,在运行的过程中才知道我们要调用什么类的什么方法,在执行的时候才明确下来,但是你那些编译阶段的校验以及一些安全机制的操作 仍然 是不能少的,所以你在执行的时候依然要做校验等安全机制的操作,所以反射性能慢
- 动态生成字节码
- 动态编译生成类
新建对象拥有十个字段,进行两百万次调用赋值比较结果:
java程序在运行前需要经历以下过程:编写java源代码->字节码->机器码->运行。在这个流程中,我们可以参与,并且主动权最大的是在第一步,源代码的编写,也就是定义程序的执行逻辑。在由java源代码->字节码的过程中,主要是javac编译器在工作,javac完成将java源代码转换成符合jvm规范的字节码。在这个转换的过程中,javac主要的做了3个工作:
-
词法语法分析:词法分析将源代码字符流分解成一个个的标记(Token),语法分析将标记序列构造成抽象语法树(AST)。
-
注解处理器处理:注解处理器可以对AST进行一定的更新处理,更新的逻辑具体是什么,取决于注解处理器的实现。
-
语义分析和字节码生成:对抽象语法树进行语义分析,完成语法糖的解析等,最终生成字节码(这个过程不是本文的重点,进行了简化说明)。
自从Java 6开始,javac开始支持JSR 269 Pluggable Annotation Processing API规范,也就是说在满足该规范的前提下,我们可以实现自己的注解处理器,来实现对javac编译器编译过程的人为干预。有了这个,我们就可以在java程序的编译阶段做很多有意思的事情了(有没有觉得注解处理器是观察者模式的一种应用,只不过,我们常用的观察者工作的运行期,注解处理器工作在编译期)。mapstruct是基于JSR 269实现的,JSR 269是JDK引进的一种规范。有了它,能够实现在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。
3、使用姿势
3.1 maven引入MapStruct依赖
<properties> <dependency> |
---|
3.2 新建一个抽象类或接口
通用操作场景:基础转换(字段名称完全一致,类型可以不同,但是支持的可转换范围)
工厂模式使用:
@Mapper |
---|
Spring 自动注入:
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) @Autowired |
---|
- 场景1:DTO字段userName 赋值给PO字段accountName
@Mapping(source = "accountName", target = "userName") |
---|
- 场景2:PO时间字段Date/LocalDateTime类型赋值给PO字符串类型,使用日期格式化
@Mapping(source = "dateTime", target = "dateTime",dateFormat = "yyyy-MM-dd HH:mm:ss") |
---|
- 场景3:PO浮点数字赋值DTO 字符串并保留2位小数
@Mapping(source = "doubleVal", target = "doubleVal",numberFormat = "0.00") |
---|
注意:必须是double/float 转string,才可以保留,且会四舍五入。相同类型转换不会保留指定位小数。
- 场景4:PO内有对象赋值给DTO内的对象
@Mapping(source = "testItemDTO", target = "testItemPO") |
---|
- 场景5:批量转换
public abstract TestPO dto2Po(TestDTO testDTO); |
---|
- 场景6:设置默认值
@Mapping(target = "another",constant = "99") |
---|
- 场景7:多字段赋值
@Mapping(source = "testDTO.id", target = "id") |
---|
- 场景8:设置不转换字段
@Mapping(target = "id", ignore = true) |
---|
- 场景9:调用其他映射器
@Mapper(uses = TestItemConvert.class) |
---|
- 场景10:后置特殊字段处理
public abstract TestPO dto2Po(TestDTO testDTO); |
---|
- 场景11:两个对象大部分字段都相同,想赋值指定字段
@BeanMapping(ignoreByDefault = true) 含义是忽略所有字段
@BeanMapping(ignoreByDefault = true) |
---|
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)