您的位置 首页 java

如何优雅的转换 Bean 对象?先这样,然后这样

背景

我们的故事要从一个风和日丽的下午开始说起!
这天,外包含在位置上写代码~外包含根据如下定义
PO(persistant object): 持久化对象,可以看成是与数据库中的表相映射的 java 对象。最简单的 PO 就是对应数据库中某个表中的一条记录。
VO(view object): 视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
BO(business 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());
1234
  

//省略一堆
return carVo;
画外音:看到这一串代码是不是特别亲切,我接手过一堆外包留下的代码,就是这么写的,一坨屎山!一类几千行,一半都在set属性。
恰巧,阿雄打水路过!鸡贼的阿雄瞄了一眼外包韩的屏幕,看到外包韩的这一系列代码!上去进行一顿教育,觉得不够优雅!阿雄觉得,应该用BeanUtils.copyProperties来简化书写,像下面这样!

 CarPo carPo = this.carDao.selectById(1L);
CarVo carVo = new CarVo();
BeanUtils.copyProperties(carPo, carVo);
return carVo;
1234
  

可是,外包韩盯着这段代码,说道:“网上不是说反射效率慢,你这么写,没有性能问题么?”
阿雄说道:” 如果是用Apache的BeanUtil类,确实有很大的性能问题,像阿里巴巴的代码扫描插件,都禁止用该类,如下所示!”

“但是,如果采用的是像Spring的BeanUtils类,要在调用次数足够多的时候,你才能明显的感受到卡顿。”阿雄补充道。
“哇,阿雄真棒!“外包韩兴奋不已!
看着这办公室基情满满的氛围。一旁正在拖地的清洁工——扫地烟,他决定不再沉默。
只见扫地烟扔掉手中的拖把,得瑟的说道”我们不考虑性能。从拓展性角度看看!BeanUtils还是有很多问题的!”
复制对象时字段类型不一致,导致复制不上,你怎么解决?自己拓展?
复制对象时字段名称不一致,例如CarPo里叫carName,CarVo里叫name,导致赋值不上,你怎么解决?自己拓展?
如果是集合类的复制,例如List转换为List,你怎么处理?
(省略一万字…)
“那应该怎么办呢?”听了扫地烟的描述,外包韩疑惑的问道!
“很简单,其实我们在转换bean的过程中,set这些逻辑是固定的,唯一变化的就是转换规则。因此,如果我们只需要书写转换规则,转换代码由系统根据规则自动生成,就方便很多了!还是用上面的例子,CarPo里叫carName,CarVo里叫name,属性名称不一致。我们就通过一个注解

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

指定对应转换规则。系统识别到这个注解,就会生成代码

 carVo.setName(carPo.getCarName())
1
  

如果能以这样的方式,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>
1234567891011
  

在准备两个实体类,为了方便演示,用了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);
1234567891011121314151617181920212223242526272829303132
  

输出如下

 CarVo(id=1, brand=BMW)
1
  

可以看到,carPo的属性值复制给了carVo。当然,在这种情况下,功能和BeanUtils是差不多的,体现不出优势!嗯,我们放在后面说,我们先来说说原理!

原理

其实原理就是MapStruct插件会识别我们的接口,生成一个实现类,在实现类中,为我们实现了set逻辑!例如,上面的例子中,给CarCovertBasic接口,生成了一个实现类CarCovertBasicImpl,我们可以用反编译工具看到源码如下图所示

如何优雅的转换 Bean 对象?先这样,然后这样

下面,我们来说说优势

优势

(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);
1234567891011121314
  

输出如下

 CarVo(id=1, brand=BMW, name=宝马)
1
  

可以看到carVo已经能识别到carPo中的carName属性,并复制成功。反编译的图如下

如何优雅的转换 Bean 对象?先这样,然后这样

画外音:如果有多个映射关系可以用@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);
}
123456789
  

如代码所示,我们增加了一个toConvertVos方法即可,mapStruct生成代码的时候,会帮我们去循环调用toConvertVo方法,给大家看一下反编译的代码,就一目了然

如何优雅的转换 Bean 对象?先这样,然后这样

(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;
}
123456789101112131415161718192021
  

接口就可以这么写

 @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);
}
1234567891011121314151617181920212223242526272829303132333435
  

直接增加接口即可,插件在生成代码的时候,会帮我们自动组装,看看下面的反编译代码就一目了然。

如何优雅的转换 Bean 对象?先这样,然后这样

(5)其他
关于MapStruct还有其他很多的高级功能,我就不一一介绍了。

总结

本文介绍了,在项目里如何优雅的转换Bean,希望大家有所收获!

文章来源:智云一二三科技

文章标题:如何优雅的转换 Bean 对象?先这样,然后这样

文章地址:https://www.zhihuclub.com/194210.shtml

关于作者: 智云科技

热门文章

评论已关闭

4条评论

  1. The relatively small amplitude of the dopamine effect, compared with the effects of high K and glutamate, may be explained in different ways Chiaradonna F, Sacco E, Manzoni R, et al

  2. Post diuretic FDG PET CT when was done pre and post therapy for patients in group B was able to give us a proper evaluation of the primary bladder lesion besides its role in the detection of metastasis in the rest of the body, which helped in proper decision making and accurate therapeutic plan for these patients

网站地图