点击上方蓝色“方志朋”,选择“设为星标”

回复“666”获取独家整理的学习资料!

背景

我们的故事要从一个风和日丽的下午开始说起!

这天,外包韩在位置上写代码~外包韩根据如下定义:

  • PO(Persistant Object):持久化对象。可以看成是与数据库中的表相映射的 Java 对象。最简单的 PO 就是对应数据库中某个表中的一条记录。

  • VO(View Object):视图对象,用于展示层。它的作用是把某个指定页面(或组件)的所有数据封装起来。

  • BO(Business Object):业务对象。主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。

  • DTODO(省略......)

将 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<CarPo> 转换为 List<CarVo>,你怎么处理?

  • (省略一万字……)

“那应该怎么办呢?”,听了扫地烟的描述外包韩疑惑的问道。

“很简单。其实我们在转换 Bean 的过程中 set 这些逻辑是固定的,唯一变化的就是转换规则。因此,如果我们只需要书写转换规则,转换代码由系统根据规则自动生成,就方便很多了!还是用上面的例子,CarPo 里叫 carName,CarVo 里叫 name,属性名称不一致。我们就通过一个注解指定对应转换规则。像下面这样:

@Mapping(source = "carName", target = "name")

系统识别到这个注解,就会生成代码:

carVo.setName(carPo.getCarName())

“如果能以这样的方式 set 代码由系统自动生成,那么灵活性将大大加强。而且这种方式不存在性能问题”,扫地烟补充道。

“那这些 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 原理

其实原理就是 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<CarPo> 转换为 List<CarVo> 怎么办呢?简单,接口里加一个方法就行:

@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 方法的时候,MapStruct 会自动调用 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,希望大家有所收获!还想听到其他关于阿雄的故事吗?请记得关注“孤独烟”!

热门内容:
  • 突然就懵了!面试官问我:线程池中多余的线程是如何回收的?

  • IntelliJ IDEA 15款 神级超级牛逼插件推荐(自用,真的超级牛逼)

  • 一款 Java 开源的 Spring Boot 即时通讯 IM 聊天系统

  • 还在用if(obj!=null)做非空判断?带你快速上手Optional实战性理解!

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡

如何优雅的转换 Bean 对象?相关推荐

  1. 如何优雅的转换 Bean 对象!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 背景 我们的故事要从一个风和日丽的下午开始说起! 这天,外 ...

  2. Json转换Bean太过复杂?试试阿里巴巴的FastJSON

    阿里巴巴的JSON和BEAN的快速转化库FastJSON FastJSON 阿里巴巴JSON库:fastJson,既能解析JSON为Bean对象,又能转换Bean对象为JSON字符串]() 为什么使用 ...

  3. Java JSON、XML文件/字符串与Bean对象互转解析

    前言      在做web或者其他项目中,JSON与XML格式的数据是大家经常会碰见的2种.在与各种平台做数据对接的时候,JSON与XML格式也是基本的数据传递格式,本文主要简单的介绍JSON/XML ...

  4. IoC-spring 的灵魂(带你轻松理解IOC思想及bean对象的生成过程)

    在理解任何技术之前,我都会问自己一个问题:它的产生是为了解决什么样的问题,以及如何解决这些问题?希望你能在本篇文章中找到答案-- (由于大家对Ioc应该是经常使用了,所以这里不会告诉你应该怎么样使用, ...

  5. mongodb中Gson和java##Bean对象转化类

    此类使用感觉比较繁琐, 每个字段加注解才可以使用, 不如mongoTemplate使用方便, 但如果使用mongo客户端的话, 还是比手动拼接快一点, 所以贴在这儿 package com.iwher ...

  6. Spring源码分析-如何获取Bean对象

    导语   在上篇博客中 介绍了关于BeanFactory和FactoryBean相关的操作,并且查看了在两个操作中他们具体的代码有那些,这篇博客主要就是顺着上篇博客思路继续来分析Bean对象的获取.下 ...

  7. Java POJO Bean 对象与 Web Form 表单的自动装配

    PS: 我一直在找寻为什么 struts2有自动将form字段和getter setter 自动 匹配的功能, 这篇文章解答了我的疑惑 深度剖析:Java POJO Bean 对象与 Web Form ...

  8. java 对象验证非空_判断Bean对象指定字段非空

    判断Bean对象指定字段非空. 方案: 在bean对象上增加注解,指定字段非空,返回异常信息有明确字段描述,省去不必要的if.else判断. 新建注解类 /** * 参数校验,判断字段非空.返回异常文 ...

  9. Spring源码阅读之bean对象的创建过程

    Spring源码阅读之bean对象的创建过程 ​ Spring是通过IOC容器来管理对象的,该容器不仅仅只是帮我们创建了对象那么简单,它负责了对象的整个生命周期-创建.装配.销毁.这种方式成为控制反转 ...

最新文章

  1. 干货 | 详解对象检测模型中的Anchors
  2. maven install 报错 source 1.5 中不支持 lambda 表达式
  3. Python使用tpot获取最优模型、将最优模型应用于交叉验证数据集(5折)获取数据集下的最优表现,并将每一折(fold)的预测结果、概率、属于哪一折与测试集标签、结果、概率一并整合输出为结果文件
  4. BCH交易量快速增长,年内增幅超比特币和莱特币
  5. LAMP之二:LAMP的性能测试以及安装xcache,为php加速
  6. InvocationTargetException异常解析
  7. selenium - webdriver常用方法
  8. C++Primer再学习(3)
  9. go语言结构体标签的意义
  10. 按季度分类汇总_1分钟实现:按某列内容分类汇总,分页打印 Excel 表格
  11. apache2.2.21下为codeigniter配置url地址重写
  12. ScrollView中嵌套recycleView 出现的不显示,显示不全,终极解决方案
  13. android中页面跳转以及数据在Activity之间的传递
  14. linux系统官方版下载 百度云,百度网盘linux版
  15. python3调用arcpy地理加权回归_分析地理加权回归分析结果的操作方法
  16. Beetlsql自学笔记
  17. sg11解密 php解密 SourceGuardian解密sg_load解密去除域名IP授权
  18. Python使用Regular入门
  19. 英语foteball足球
  20. Win7如何利用系统放大镜工具更改用户密码

热门文章

  1. 二叉树的镜像(数组,前后 遍历重建二叉树)
  2. 01_创建一个新的activityactivity配置清单文件
  3. js绑定事件和解绑事件
  4. JS中简单原型的使用
  5. 创建对象_工厂方法(Factory Method)模式 与 静态工厂方法
  6. css sprites之圆角
  7. 【青少年编程】【四级】数字反转
  8. Matlab与线性代数 -- 线性间隔向量
  9. 【C++】枚举类型应用
  10. 对数函数定义域和值域_呆哥数学每日一题 —— 复合函数值域