【原创】如何优雅的转换Bean对象
背景
我们的故事要从一个风和日丽的下午开始说起!
这天,外包韩在位置上写代码~外包韩根据如下定义
- PO(persistant object):持久化对象,可以看成是与数据库中的表相映射的 java 对象。最简单的 PO 就是对应数据库中某个表中的一条记录。
- VO(view object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
- BO(view object):业务对象,主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。
- DTO、DO(省略......)
将Bean进行逐一分类!例如一个car_tb的表,于是他有了两个类,一个叫CarPo,里头属性和表字段完全一致。另一个叫CarVo,用于页面上的Car显示!
但是外包韩在做CarPo到CarVo转换的时候,代码是这么写的,伪代码如下:
CarPo carPo = this.carDao.selectById(1L);
CarVo carVo = new CarVo();
carVo.setId(carPo.getId());
carVo.setName(carPo.getName());
//省略一堆
return carVo;
画外音:看到这一串代码是不是特别亲切,我接手过一堆外包留下的代码,就是这么写的,一坨屎山!一类几千行,一半都在set属性。
恰巧,阿雄打水路过!鸡贼的阿雄瞄了一眼外包韩的屏幕,看到外包韩的这一系列代码!上去进行一顿教育,觉得不够优雅!阿雄觉得,应该用BeanUtils.copyProperties
来简化书写,像下面这样!
CarPo carPo = this.carDao.selectById(1L);
CarVo carVo = new CarVo();
BeanUtils.copyProperties(carPo, carVo);
return carVo;
可是,外包韩盯着这段代码,说道:"网上不是说反射效率慢,你这么写,没有性能问题么?"
阿雄说道:" 如果是用Apache的BeanUtil类,确实有很大的性能问题,像阿里巴巴的代码扫描插件,都禁止用该类,如下所示!"
"但是,如果采用的是像Spring的BeanUtils类,要在调用次数足够多的时候,你才能明显的感受到卡顿。"阿雄补充道。
"哇,阿雄真棒!"外包韩兴奋不已!
看着这办公室基情满满的氛围。一旁正在拖地的清洁工------扫地烟,他决定不再沉默。
只见扫地烟扔掉手中的拖把,得瑟的说道"我们不考虑性能。从拓展性角度看看!BeanUtils还是有很多问题的!"
- 复制对象时字段类型不一致,导致赋值不上,你怎么解决?自己拓展?
- 复制对象时字段名称不一致,例如CarPo里叫carName,CarVo里叫name,导致赋值不上,你怎么解决?自己拓展?
- 如果是集合类的复制,例如List转换为List,你怎么处理?
(省略一万字....)
"那应该怎么办呢?"听了扫地烟的描述,外包韩疑惑的问道!
"很简单,其实我们在转换bean的过程中,set这些逻辑是固定的,唯一变化的就是转换规则。因此,如果我们只需要书写转换规则,转换代码由系统根据规则自动生成,就方便很多了!还是用上面的例子,CarPo里叫carName,CarVo里叫name,属性名称不一致。我们就通过一个注解
@Mapping(source = "carName", target = "name"),
指定对应转换规则。系统识别到这个注解,就会生成代码
carVo.setName(carPo.getCarName())
如果能以这样的方式,set代码由系统自动生成,那么在bean转换逻辑方面,灵活性将大大加强,而且这种方式不存在性能问题!"扫地烟补充道!
"那这些set逻辑,由什么工具来生成呢?"外包韩和阿雄一起问道!
"工具的名字叫MapStruct!"
ok,上面的故事到了这里,就结束了!不需要问结局,结局只有一个,外包韩和阿雄幸福美满的...(省略10000字)...
那么我们开始具体来说一说MapStruct!
MapStruct的教程
这里从用法、原理、优势三个角度来介绍一下这个插件,至于详细教程,还是看官方文档吧。
用法
引入pom文件如下
<dependency>
<groupId>org.mapstruct</groupId>
<!-- jdk8以下就使用mapstruct -->
<artifactId>mapstruct-jdk8</artifactId>
<version>1.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.2.0.Final</version>
</dependency>
在准备两个实体类,为了方便演示,用了lombok
插件。
准备两个实体类,一个是CarPo
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CarPo {
private Integer id;
private String brand;
}
还有一个是CarVo
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CarVo {
private Integer id;
private String brand;
}
再来一个转换接口
@Mapper
public interface CarCovertBasic {
CarCovertBasic INSTANCE =
Mappers.getMapper(CarCovertBasic.class);
CarVo toConvertVo(CarPo source);
}
测试代码如下:
//实际中从数据库取
CarPo carPo = CarPo.builder().id(1)
.brand("BMW")
.build();
CarVo carVo = CarCovertBasic.INSTANCE.toConvertVo(carPo);
System.out.println(carVo);
输出如下
CarVo(id=1, brand=BMW)
可以看到,carPo的属性值复制给了carVo。当然,在这种情况下,功能和BeanUtils
是差不多的,体现不出优势!嗯,我们放在后面说,我们先来说说原理!
原理
其实原理就是MapStruct插件会识别我们的接口,生成一个实现类,在实现类中,为我们实现了set逻辑!
例如,上面的例子中,给CarCovertBasic接口,实现了一个实现类CarCovertBasicImpl,我们可以用反编译工具看到源码如下图所示
下面,我们来说说优势
优势
(1)两个类型属性不一致
此时CarPo的一个属性为carName,而CarVo对应的属性为name!
我们在接口上增加对应关系即可,如下所示
@Mapper
public interface CarCovertBasic {
CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class);
@Mapping(source = "carName", target = "name")
CarVo toConvertVo(CarPo source);
}
测试代码如下
CarPo carPo = CarPo.builder().id(1)
.brand("BMW")
.carName("宝马")
.build();
CarVo carVo = CarCovertBasic.INSTANCE.toConvertVo(carPo);
System.out.println(carVo);
输出如下
CarVo(id=1, brand=BMW, name=宝马)
可以看到carVo已经能识别到carPo中的carName属性,并赋值成功。反编译的图如下
画外音:
如果有多个映射关系可以用@Mappings注解,嵌套多个@Mapping注解实现,后文说明!
(2)集合类型转换
如果我们要从List转换为List怎么办呢?
简单,接口里加一个方法就行
@Mapper
public interface CarCovertBasic {
CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class);
@Mapping(source = "carName", target = "name")
CarVo toConvertVo(CarPo source);
List<CarVo> toConvertVos(List<CarPo> source);
}
如代码所示,我们增加了一个toConvertVos方法即可,mapStruct生成代码的时候,会帮我们去循环调用toConvertVo方法,给大家看一下反编译的代码,就一目了然
(3)类型不一致
在CarPo加一个属性为Date类型的createTime,而在CarVo加一个属性为String类型的createTime,那么代码如下
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CarPo {
private Integer id;
private String brand;
private String carName;
private Date createTime;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CarVo {
private Integer id;
private String brand;
private String name;
private String createTime;
}
接口就可以这么写
@Mapper
public interface CarCovertBasic {
CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class);
@Mappings({
@Mapping(source = "carName", target = "name"),
@Mapping(target = "createTime", expression = "java(com.guduyan.util.DateUtil.dateToStr(source.getCreateTime()))")
})
CarVo toConvertVo(CarPo source);
List<CarVo> toConvertVos(List<CarPo> source);
}
这样在代码中,就能解决类型不一致的问题!在生成set方法的时候,自动调用DateUtil类进行转换,由于比较简单,我就不贴反编译的图了!
(4)多对一
在实际业务情况中,我们有时候会遇到将两个Bean映射为一个Bean的情况,假设我们此时还有一个类为AtrributePo,我们要将CarPo和AttributePo同时映射为CarBo,我们可以这么写
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttributePo {
private double price;
private String color;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CarBo {
private Integer id;
private String brand;
private String carName;
private Date createTime;
private double price;
private String color;
}
接口改变如下
@Mapper
public interface CarCovertBasic {
CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class);
@Mappings({
@Mapping(source = "carName", target = "name"),
@Mapping(target = "createTime", expression = "java(com.guduyan.util.DateUtil.dateToStr(source.getCreateTime()))")
})
CarVo toConvertVo(CarPo source);
List<CarVo> toConvertVos(List<CarPo> source);
CarBo toConvertBo(CarPo source1, AttributePo source2);
}
直接增加接口即可,插件在生成代码的时候,会帮我们自动组装,看看下面的反编译代码就一目了然。
(5)其他
关于MapStruct还有其他很多的高级功能,我就不一一介绍了。大家可以参考下面的文档,在用到的时候自行翻阅即可!
文档地址:https://mapstruct.org/documentation/reference-guide/
总结
本文介绍了,在项目里如何优雅的转换Bean,希望大家有所收获!
还想听到其他关于阿雄的故事么,请记得关注"孤独烟!"
【原创】如何优雅的转换Bean对象的更多相关文章
- Java—JSON串转换成实体Bean对象模板
介绍 模板需求说明 开发中经常遇到前端传递过来的JSON串的转换,后端需要解析成对象,有解析成List的,也有解析成Map的. 依赖 <dependency> <groupId& ...
- 把实体bean对象转换成DBObject工具类
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util ...
- json转换成对象
在json转换成对象时,json的key会与java 类的字段一一对应.如果没有映射上的java字段会在该数据类型上填充默认值,如int 0,String null 等. 没有映射的json key在 ...
- Java JSON、XML文件/字符串与Bean对象互转解析
前言 在做web或者其他项目中,JSON与XML格式的数据是大家经常会碰见的2种.在与各种平台做数据对接的时候,JSON与XML格式也是基本的数据传递格式,本文主要简单的介绍JSON/XML ...
- mongodb中Gson和java##Bean对象转化类
此类使用感觉比较繁琐, 每个字段加注解才可以使用, 不如mongoTemplate使用方便, 但如果使用mongo客户端的话, 还是比手动拼接快一点, 所以贴在这儿 package com.iwher ...
- request请求转换成对象。
1)前端post数据过来,key和val键值对会有很多,这个时候往后端进行插值的时候,最好将这些键值对转换成对象进行处理. 使用common-beanutils 来将前端传递过来的map直接转换成对象 ...
- [zhuanzai]Bean对象注入失败 .NoSuchBeanDefinitionException: No qualifying bean of type..
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying b ...
- Java进阶知识17 Spring Bean对象的创建细节和创建方式
本文知识点(目录): 1.创建细节 1) 对象创建: 单例/多例 2) 什么时候创建? 3)是否延迟创建(懒加载) 4) 创建对象之后, ...
- 向虚拟机注册钩子,实现Bean对象的初始化和销毁方法
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 有什么方式,能给代码留条活路? 有人说:人人都是产品经理,那你知道吗,人人也都可以是 ...
随机推荐
- LInux下Posix的传统线程示例
简介 Linux线程是需要连接pthreat库,线程的使用比进程更灵活,需要注意的是线程间的互斥,或者说是资源共享问题. C++11之后,C++标准库也引入了线程,并且使用非常方便,以后再介绍,这里先 ...
- 安卓APP开发的初步了解
今天成功安装了Android Studio 并且对APP的开发框架结构进行了初步了解 如上图:app基本结构情况 下面来仔细解释一下各个方面目录的作用 首先 manifests目录:包含Android ...
- LeetCode 861翻转矩阵后得分详细解法
1. 题目内容 有一个二维矩阵 A 其中每个元素的值为 0 或 1 . 移动是指选择任一行或列,并转换该行或列中的每一个值:将所有 0 都更改为 1,将所有 1 都更改为 0. 在做出任意次数的移动后 ...
- MSF常用命令备忘录
msf下的命令 set session x:设置要攻击的session #监听端口反弹PHP shell use exploit/multi/handler set payload php/meter ...
- volatile的特性代码验证
一 . 可见性(visibility) volatile关键字修饰的变量,如果值发生了改变,其他线程会立刻获取到,从而避免了出现脏读的情况. public class TestVolatile { p ...
- Caused by: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value f ...
- 图论算法(二)最短路算法:Floyd算法!
最短路算法(一) 最短路算法有三种形态:Floyd算法,Shortset Path Fast Algorithm(SPFA)算法,Dijkstra算法. 我个人打算分三次把这三个算法介绍完. (毕竟写 ...
- 关于华为否认HKSP来自官方的一点看法
今天刷手机,无意看到一则消息: 华为否认提交给 Linux 内核的不安全补丁 HKSP 来自官方 这个消息吸引我的原因有几点: 华为被制裁刚满一年,现在由于新冠疫情影响,感觉又处于很敏感的时期: 华为 ...
- vue报错vue-router.esm.js?8c4f:2007 Uncaught (in promise) NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated"}
今天在写vue项目配置好路由点击菜单时,突然在控制台报错. 错误信息如下: Uncaught (in promise) NavigationDuplicated {_name: "Navig ...
- Java并发--三大性质
一.多线程的三大性质 原子性:可见性.有序性 二.原子性 原子性介绍 原子性是指:一个操作时不可能中断的,要么全部执行成功要么全部执行失败,有着同生共死的感觉.即使在多线程一起执行的时候,一个操作一旦 ...