背景

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

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

  • 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对象的更多相关文章

  1. Java—JSON串转换成实体Bean对象模板

    介绍 模板需求说明   开发中经常遇到前端传递过来的JSON串的转换,后端需要解析成对象,有解析成List的,也有解析成Map的. 依赖 <dependency> <groupId& ...

  2. 把实体bean对象转换成DBObject工具类

    import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util ...

  3. json转换成对象

    在json转换成对象时,json的key会与java 类的字段一一对应.如果没有映射上的java字段会在该数据类型上填充默认值,如int 0,String null 等. 没有映射的json key在 ...

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

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

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

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

  6. request请求转换成对象。

    1)前端post数据过来,key和val键值对会有很多,这个时候往后端进行插值的时候,最好将这些键值对转换成对象进行处理. 使用common-beanutils 来将前端传递过来的map直接转换成对象 ...

  7. [zhuanzai]Bean对象注入失败 .NoSuchBeanDefinitionException: No qualifying bean of type..

    nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying b ...

  8. Java进阶知识17 Spring Bean对象的创建细节和创建方式

    本文知识点(目录): 1.创建细节         1) 对象创建: 单例/多例         2) 什么时候创建?         3)是否延迟创建(懒加载)         4) 创建对象之后, ...

  9. 向虚拟机注册钩子,实现Bean对象的初始化和销毁方法

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 有什么方式,能给代码留条活路? 有人说:人人都是产品经理,那你知道吗,人人也都可以是 ...

随机推荐

  1. 001_centos7下比特币源码编译安装

    今天我们介绍比特币的源码安装过程,是利用编译安装的 首先安装依赖 yum install -y boost-devel qt-devel protobuf-devel qrencode-devel l ...

  2. 家庭记账本APP开发准备(二)

    今天学习了选项卡,为记账本的分类做了准备.主登录界面进行了优化,但仍未实现各个组件之间的跳转. 选项卡 activity_main.xml <?xml version="1.0&quo ...

  3. echarts全国疫情统计可视化地图(第一阶段)

    DBUtil.java package com.helloechart; import java.sql.Connection; import java.sql.DriverManager; impo ...

  4. Spring Boot Logback 默认配置

    Spring Boot Logback 默认配置 标签(空格分隔): Spring Boot Intro(介绍) Spring Boot 1.5.9 默认使用的日志框架是 Logback. 生效的默认 ...

  5. C#LeetCode刷题之#59-螺旋矩阵 II(Spiral Matrix II)

    目录 问题 示例 分析 问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3678 访问. 给定一个正整数 n,生成一 ...

  6. 使用 .NET Core 3.x 构建 RESTFUL Api (续)

    关于Entity Model vs 面向外部的Model Entity Framework Core 使用 Entity Model 用来表示数据库里面的记录. 面向外部的Model 则表示要传输的东 ...

  7. 【Spring注解驱动开发】自定义组件如何注入Spring底层的组件?看了这篇我才真正理解了原理!!

    写在前面 最近,很多小伙伴出去面试都被问到了Spring问题,关于Spring,细节点很多,面试官也非常喜欢问一些很细节的技术点.所以,在 Spring 专题中,我们尽量把Spring的每个技术细节说 ...

  8. VScode+PicGo+Github+jsdelivr使用图床书写Markdown

    本文讲述使用Github作为图床,VScode搭配Picgo插件书写Markdown,并使用jsdelivr进行CDN加速的配置流程. 准备阶段 首先进行以下准备工作,都很简单,不再赘述. 注册Git ...

  9. eclipse项目文件夹整理

    1.点击倒三角 2.系统默认为Projects,选择第二个working sets 3.点击Configure Working Sets,点new 4.点击后,选中点Add 5.添加一个名字,Fins ...

  10. JavaScript学习系列博客_18_JavaScript中的匿名函数

    匿名函数 - 用函数声明的方式创建一个函数时,不加函数名称. function sum(){ console.log("我是函数sum")} - 不加名称,这样写浏览器是会报错的. ...