背景

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

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

  • 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. CSS表单与数据表(下)

    2.表单 表单是用户输入内容的地方.表单涉及的控件很多,而且一直很难给它们应用样式.无法控制样式的部分,可以通过自定义控件来解决. 2.1 简单的表单 2.1.1 fieldset与legend fi ...

  2. 【Python笔记】2020年7月30日练习【python用input函数输入一个列表】

    练习课题链接:廖雪峰-Python教程-高级特性-迭代 学习记录: 1.Python当中类似于 三目运算符 的应用 2.Python用input函数输入一个列表 代码实例:对用户输入的一组数字转化成l ...

  3. 十多位全球技术专家,为你献上近十个小时的.Net微服务介绍

    .Net Conf: Focus on Microservices 是 .Net Conf 社区在 2020 年 7 月 30 日举办的线上分享活动.整个活动视频长达近 10 个小时.今天我们来看看都 ...

  4. java流程控制语句if

    一 if语句 if语句是指如果满足某种条件,就进行某种处理. 格式: if (条件语句){ 执行语句; …… } 流程图: 例如: public class IfDemo01 { public sta ...

  5. P6087 [JSOI2015]送礼物 01分数规划+单调队列+ST表

    P6087 [JSOI2015]送礼物 01分数规划+单调队列+ST表 题目背景 \(JYY\) 和 \(CX\) 的结婚纪念日即将到来,\(JYY\) 来到萌萌开的礼品店选购纪念礼物. 萌萌的礼品店 ...

  6. jQuery的事件与 动画

    什么是事件: 事件的本质是委托. Jquery的 方法: $().css(); $().click(); 等等. 鼠标的事件: 区别在于:mouseover与mouseout再进入或离开后会执行这两个 ...

  7. 收集整理element-ui 表格组件的常用操作方法

    一.简单的表格行内编辑效果 原理是通过Css控制绑定的输入控件与显示值,在选中行样式下对控件进行隐藏或显示. 1.注意下样式的设置 2.change事件 @change="handleEdi ...

  8. Mybatis-08-动态SQL

    动态SQL 什么是动态SQL? 根据不同的条件生成不同的SQL语句. if choose(where,otherwise) trim(where,set) foreach 搭建环境 create ta ...

  9. 配置 Eureka Server 集群

    简介 为了使 Eureka Server 实现高可用,我们需要为它配置集群.这样当有一台 Eureka Server 有故障时,集群中的其他 Server 可以进行代替.Eureka 集群之中的 No ...

  10. js、jQuery、ajax面试题

    1.javascript的typeof返回哪些数据类型. 答案:string,boolean,number,undefined,function,object 2.例举3种强制类型转换和2种隐式类型转 ...