甩掉 BeanUtils!试试这个优雅的方法...

2023-10-22 01:50

本文主要是介绍甩掉 BeanUtils!试试这个优雅的方法...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

点击“终码一生”,关注,置顶公众号

每日技术干货,第一时间送达!

1

什么是MapStruct

1.1 JavaBean 的困扰

对于代码中 JavaBean之间的转换, 一直是困扰我很久的事情。在开发的时候我看到业务代码之间有很多的 JavaBean 之间的相互转化, 非常的影响观感,却又不得不存在。我后来想的一个办法就是通过反射,或者自己写很多的转换器。

第一种通过反射的方法确实比较方便,但是现在无论是 BeanUtils, BeanCopier 等在使用反射的时候都会影响到性能。虽然我们可以进行反射信息的缓存来提高性能。但是像这种的话,需要类型和名称都一样才会进行映射,有很多时候,由于不同的团队之间使用的名词不一样,还是需要很多的手动 set/get 等功能。

第二种的话就是会很浪费时间,而且在添加新的字段的时候也要进行方法的修改。不过,由于不需要进行反射,其性能是很高的。

1.2 MapStruct 带来的改变

MapSturct 是一个生成类型安全,高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。

  • 注解处理器

  • 可以生成 JavaBean 之间那的映射代码

  • 类型安全,高性能,无依赖性

2

MapStruct 入门

2.1 添加依赖

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.20</version><scope>provided</scope>
</dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-jdk8</artifactId><version>${org.mapstruct.version}</version>
</dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version>
</dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.1.0</version>
</dependency>

2.2 po类

@Data
public class User {private Integer id;private String name;private String address;private Date birth;
}

2.3 dto类

@Data
public class UserDto implements Serializable {private Integer id;private String name;private String address;private Date birth;
}

2.4 创建转换接口

//可以使用abstract class代替接口
@Mapper
public interface UserMapper {UserDto userToUserDto(User user);//集合List<UserDto> userToUserDto(List<User> users);
}

2.5 测试方法

@Test
public void userPoToUserDto() {User user =new User();user.setId(1);user.setName("myx");user.setAddress("河北沧州");user.setBirth(new Date());UserMapper mapper = Mappers.getMapper(UserMapper.class);UserDto userDto = mapper.userToUserDto(user);System.out.println(userDto);
}

2.6 运行效果

2.7 查看编译的class

底层通过自动取值赋值操作完成

 

3

MapStruct优点分析

3.1 性能高

这是相对反射来说的,反射需要去读取字节码的内容,花销会比较大。而通过 MapStruct 来生成的代码,其类似于人手写。速度上可以得到保证。

3.2 使用简单

如果是完全映射的,使用起来肯定没有反射简单。用类似 BeanUtils 这些工具一条语句就搞定了。但是,如果需要进行特殊的匹配(特殊类型转换,多对一转换等),其相对来说也是比较简单的。

基本上,使用的时候,我们只需要声明一个接口,接口下写对应的方法,就可以使用了。当然,如果有特殊情况,是需要额外处理的。另外,欢迎关注我们,公号终码一生,后台回复“资料”,获取视频教程和最新面试资料。

3.3 代码独立

生成的代码是对立的,没有运行时的依赖。

3.4 易于 debug

在我们生成的代码中,我们可以轻易的进行 debug。

4

MapStruct使用案例

4.1 属性名称相同

在实现类的时候,如果属性名称相同,则会进行对应的转化。通过此种方式,我们可以快速的编写出转换的方法。

4.2 属性名不相同

属性名不相同,在需要进行互相转化的时候,则我们可以通过@Mapping 注解来进行转化。

@Data
public class UserDto implements Serializable {private Integer id;private String name;private String address;private Date birth;private String password;
}@Data
public class User {private Integer id;private String name;private String address;private Date birth;private String pwd;
}@Mapper
public interface UserMapper {//单个属性//@Mapping(source = "pwd",target = "password")//多个属性@Mappings({@Mapping(source = "pwd",target = "password")})UserDto userToUserDto(User user);
}
  • source 需要转换的对接,通常是入参

  • target 转换的对接,通常是出参

  • ignore 忽略,默认false不忽略,需要忽略设置为true

  • defaultValue 默认值

  • expressions 可以通过表达式来构造一些简单的转化关系。虽然设计的时候想兼容很多语言,不过目前只能写Java代码。

@Mappings({@Mapping(source = "birthdate", target = "birth"),//属性名不一致映射@Mapping(target = "birthformat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthdate(),\"yyyy-MM-dd HH:mm:ss\"))"),//自定义属性通过java代码映射})
public PersonVo PersonToPersonVo(Person person);

这里用到演示了如何使用TimeAndFormat对time和format操作,这里必须要指定需要使用的Java类的完整包名,不然编译的时候不知道你使用哪个Java类,会报错。

@Test
public void userPoToUserDto() {User user =new User();user.setId(1);user.setName("myx");user.setAddress("河北沧州");user.setBirth(new Date());user.setPwd("123456");UserMapper mapper = Mappers.getMapper(UserMapper.class);UserDto userDto = mapper.userToUserDto(user);System.out.println(userDto);
}

4.3 转换非基础类型属性

如果subUser与subUserDto字段名称相同直接配置即可完成(对象类型,包括list)

@Data
public class UserDto implements Serializable {private Integer id;private String name;private String address;private Date birth;private String password;private List<SubUserDto> subUserDto;
}@Data
public class User {private Integer id;private String name;private String address;private Date birth;private String pwd;private List<SubUser> subUser;
}@Mappings({@Mapping(source = "pwd",target = "password"),@Mapping(source = "subUser", target = "subUserDto")
})UserDto userToUserDto(User user);

4.4 Mapper 中使用自定义的转换

有时候,对于某些类型,无法通过代码生成器的形式来进行处理。那么, 就需要自定义的方法来进行转换。这时候,我们可以在接口(同一个接口,后续还有调用别的 Mapper 的方法)中定义默认方法(Java8及之后)。

@Data
public class UserDto implements Serializable {private Integer id;private String name;private String address;private Date birth;private String password;private SubUserDto subUserDto;
}@Data
public class SubUserDto {private Boolean result;private String name;}@Data
public class User {private Integer id;private String name;private String address;private Date birth;private String pwd;private SubUser subUser;
}@Data
public class SubUser {private Integer deleted;private String name;
}@Mapper
public interface UserMapper {@Mappings({@Mapping(source = "pwd",target = "password"),@Mapping(source = "subUser", target = "subUserDto")})UserDto userToUserDto(User user);default SubUserDto subSource2subTarget(SubUser subUser) {if (subUser == null) {return null;}SubUserDto subUserDto = new SubUserDto();subUserDto.setResult(!subUser.getDeleted().equals(0));subUserDto.setName(subUser.getName()==null?"":subUser.getName());return subUserDto;}
}

只能存在一个default修饰的方法

@Test
public void userPoToUserDto() {User user =new User();user.setId(1);user.setName("myx");user.setAddress("河北沧州");user.setBirth(new Date());user.setPwd("123456");SubUser subUser =new SubUser();subUser.setDeleted(0);subUser.setName("rkw");user.setSubUser(subUser);UserMapper mapper = Mappers.getMapper(UserMapper.class);UserDto userDto = mapper.userToUserDto(user);System.out.println(userDto);
}

4.5 多转一

我们在实际的业务中少不了将多个对象转换成一个的场景。MapStruct 当然也支持多转一的操作。

@Data
public class SubUser {private Integer deleted;private String name;
}@Data
public class User {private Integer id;private String name;private String address;private Date birth;private String pwd;
}@Mapper
public interface UserMapper {@Mappings({@Mapping(source = "user.pwd",target = "password"),@Mapping(source = "subUser.name", target = "name")})NewUserDto userToUserDto(User user,SubUser subUser);}@Test
public void userPoToUserDto() {User user =new User();user.setId(1);user.setName("myx");user.setAddress("河北沧州");user.setBirth(new Date());user.setPwd("123456");SubUser subUser =new SubUser();subUser.setDeleted(0);subUser.setName("rkw");UserMapper mapper = Mappers.getMapper(UserMapper.class);NewUserDto userDto = mapper.userToUserDto(user,subUser);System.out.println(userDto);
}

4.5.1 遵循原则

  • 当多个对象中, 有其中一个为 null, 则会直接返回 null

  • 如一对一转换一样, 属性通过名字来自动匹配。因此, 名称和类型相同的不需要进行特殊处理

  • 当多个原对象中,有相同名字的属性时,需要通过 @Mapping 注解来具体的指定, 以免出现歧义(不指定会报错)。如上面的 name

属性也可以直接从传入的参数来赋值

@Mapping(source = "person.description", target = "description")
@Mapping(source = "name", target = "name")
DeliveryAddress personAndAddressToDeliveryAddressDto(Person person, String name);

4.6 更新 Bean 对象

有时候,我们不是想返回一个新的 Bean 对象,而是希望更新传入对象的一些属性。这个在实际的时候也会经常使用到。另外,欢迎关注我们,公号终码一生,后台回复“资料”,获取视频教程和最新面试资料。

@Mapper
public interface UserMapper {NewUserDto userToNewUserDto(User user);/*** 更新, 注意注解 @MappingTarget* 注解 @MappingTarget后面跟的对象会被更新。*/void updateDeliveryAddressFromAddress(SubUser subUser,@MappingTarget NewUserDto newUserDto);
}@Test
public void userPoToUserDto() {User user =new User();user.setId(1);user.setName("myx");user.setAddress("河北沧州");user.setBirth(new Date());SubUser subUser =new SubUser();subUser.setDeleted(0);subUser.setName("rkw");UserMapper mapper = Mappers.getMapper(UserMapper.class);NewUserDto userDto = mapper.userToNewUserDto(user);mapper.updateDeliveryAddressFromAddress(subUser,userDto);System.out.println(userDto);
}

4.7 map映射

@MapMapping(valueDateFormat ="yyyy-MM-dd HH:mm:ss")
public Map<String ,String> DateMapToStringMap(Map<String,Date> sourceMap);@Test
public void mapMappingTest(){Map<String,Date> map=new HashMap<>();map.put("key1",new Date());map.put("key2",new Date(new Date().getTime()+9800000));Map<String, String> stringObjectMap = TestMapper.MAPPER.DateMapToStringMap(map);
}

4.8 多级嵌套

只需要在mapper接口中定义相关的类型转换方法即可,list类型也适用

4.8.1 方式1

@Data
public class User {private Integer id;private String name;private String address;private Date birth;private Boolean isDisable;private List<SubUser> user;
}@Data
public class SubUser {private Integer deleted;private String name;private List<SubSubUser> subUser;
}@Data
public class SubSubUser {private String aaa;private String ccc;
}@Data
public class UserDto implements Serializable {private Integer id;private String name;private String address;private Date birth;private String isDisable;private List<SubUserDto> user;
}@Data
public class SubUserDto {private Integer deleted;private String name;private List<SubSubUserDto> subUser;
}@Data
public class SubSubUserDto {private String aaa;private  String bbb;
}@Mapper
public interface UserMapper {UserDto userToNewUserDto(User user);//子集字段相同方法不用编写会自动生成//孙子集字段不相同(list会自动读取此方法生成list)@Mapping(source = "ccc",target = "bbb")SubSubUserDto bbb(SubSubUser subSubUser);
}

4.8.2 方式2

通过uses配置类型转换

@Mapper(uses = {TestMapper.class})
public interface UserMapper {UserDto userToNewUserDto(User user);
}@Mapper
public interface TestMapper {@Mapping(source = "ccc",target = "bbb")SubSubUserDto bbb(SubSubUser subSubUser);
}

 

5

获取 mapper

5.1 通过 Mapper 工厂获取

我们都是通过 Mappers.getMapper(xxx.class) 的方式来进行对应 Mapper 的获取。此种方法为通过 Mapper 工厂获取。另外,欢迎关注我们,公号终码一生,后台回复“资料”,获取视频教程和最新面试资料。

如果是此种方法,约定俗成的是在接口内定义一个接口本身的实例 INSTANCE, 以方便获取对应的实例。

@Mapper
public interface SourceMapper {SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);// ......
}

这样在调用的时候,我们就不需要在重复的去实例化对象了。类似下面:

Target target = SourceMapper.INSTANCE.source2target(source);

5.2 使用依赖注入

对于 Web 开发,依赖注入应该很熟悉。MapSturct 也支持使用依赖注入,同时也推荐使用依赖注入。

@Mapper(componentModel = "spring")

5.3 依赖注入策略

可以选择是通过构造方法或者属性注入,默认是属性注入。

public enum InjectionStrategy {/** Annotations are written on the field **/FIELD,/** Annotations are written on the constructor **/CONSTRUCTOR
}

类似如此使用

@Mapper(componentModel = "cdi" injectionStrategy = InjectionStrategy.CONSTRUCTOR)

5.4 自定义类型转换

有时候,在对象转换的时候可能会出现这样一个问题,就是源对象中的类型是Boolean类型,而目标对象类型是String类型,这种情况可以通过@Mapper的uses属性来实现:

@Data
public class User {private Integer id;private String name;private String address;private Date birth;private Boolean isDisable;}@Data
public class UserDto implements Serializable {private Integer id;private String name;private String address;private Date birth;private String isDisable;
}@Mapper(uses = {BooleanStrFormat.class})
public interface UserMapper {UserDto userToNewUserDto(User user);
}public class BooleanStrFormat {public String toStr(Boolean isDisable) {if (isDisable) {return "Y";} else {return "N";}}public Boolean toBoolean(String str) {if (str.equals("Y")) {return true;} else {return false;}}
}

要注意的是,如果使用了例如像spring这样的环境,Mapper引入uses类实例的方式将是自动注入,那么这个类也应该纳入Spring容器

@Test
public void userPoToUserDto() {User user =new User();user.setId(1);user.setName("myx");user.setAddress("河北沧州");user.setBirth(new Date());user.setIsDisable(true);SubUser subUser =new SubUser();subUser.setDeleted(0);subUser.setName("rkw");UserMapper mapper = Mappers.getMapper(UserMapper.class);UserDto userDto = mapper.userToNewUserDto(user);System.out.println(userDto);
}

PS:防止找不到本篇文章,可以收藏点赞,方便翻阅查找哦。 

往期推荐

学会这个后,Postman 就可以丢掉了...

Nginx 通过 Lua + Redis 实现动态封禁 IP

你真的会写for循环吗?来看看这些常见的for循环优化方式

Spring Boot + Vue + Shiro 实现前后端分离,写得太好了!

卸载 Notepad++!事实已证明,它更牛逼…

基于 springboot 的快速开发框架,非常漂亮的脚手架!

这篇关于甩掉 BeanUtils!试试这个优雅的方法...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/258324

相关文章

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

C#中读取XML文件的四种常用方法

《C#中读取XML文件的四种常用方法》Xml是Internet环境中跨平台的,依赖于内容的技术,是当前处理结构化文档信息的有力工具,下面我们就来看看C#中读取XML文件的方法都有哪些吧... 目录XML简介格式C#读取XML文件方法使用XmlDocument使用XmlTextReader/XmlTextWr

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

oracle DBMS_SQL.PARSE的使用方法和示例

《oracleDBMS_SQL.PARSE的使用方法和示例》DBMS_SQL是Oracle数据库中的一个强大包,用于动态构建和执行SQL语句,DBMS_SQL.PARSE过程解析SQL语句或PL/S... 目录语法示例注意事项DBMS_SQL 是 oracle 数据库中的一个强大包,它允许动态地构建和执行