这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战
今天不讲设计模式了,一直坚持看我设计模式的人可能知道,设计模式专栏还没有讲完,之所以今天不讲了,是想着最后留着的是最重要的,在我们平常实战过程当中运用最多的,所以我想好好设计一下。争取让每个人看了都怒赞。哈哈 话不多说 今天我们的主角不是设计模式。
(有兴趣的可以在本文末查看,我会把链接发出来)
还是多唠叨几句,欢迎大家关注,点赞,我后面会持续输出干货,大家也可以点击我头像,查看历史干货!
copy对象
为什么要copy对象,大家都知道,在日常开发中,可能A、B、C服务,A服务调用B服务,接口的参数值是大致一样的。过来之后。 DO、DTO、VO等等,这些实体要换转。那么编写映射转换代码是一个繁琐重复且还易错的工作。
(小提示:接口一般用dto去接,返回尽量去vo返回,那么这中间会涉及到转换,比如DTO转POJO)
下面我们看看有几种copy对象的方式,谁才是最“骚”的那一位
BeanUtil类
大家可能最先想到自己实战过程中用的最多的是apache的 BeanUtil类,没错 我刚开始参加工作时,也是用的这个。
org.apache.commons.beanutils.BeanUtils.copyProperties(do, entity);
复制代码
Q:这个有什么问题没?
A: BeanUtil.copyProperties()结合手写get、set,对于简单的转换直接使用BeanUtil,复杂的转换自己手工写get、set。该方案的痛点就在于代码编写效率低、冗余繁杂还略显丑陋,并且BeanUtil因为使用了反射invoke去赋值性能不高。
只能适合bean数量较少、内容不多、转换不频繁的场景。
而且阿里开发规约明确提出(强制)
所以这里还是要切记使用的。。。
接下来看第二种
Fastjson
对 ,没错 阿里的fastjson也是可以的。
//这种方式,基本上我们都是拿字符串转json使用
StudentsDTO entity = JSON.parseObject(JSON.toJSONString(studentsDO), StudentsDTO.class);
复制代码
这种方案因为通过生成中间json格式字符串,然后再转化成目标对象。
性能可能还没有第一种好,更差一些,同时因为中间会生成json格式字符串,如果转化过多,gc会非常频繁,同时针对复杂场景支持能力不足,基本很少用。
所以第二个也不是最“骚”的那一个。接着往下看!
Spring.BeanUtils
这种方案针对apache的BeanUtils做了很多优化,整体性能提升不少,不过还是使用反射实现比不上原生代码处理,其次针对复杂场景支持能力不足。
下面我截图源码大概看下
核心就在这个for循环里,它虽然性能有所提升 但依然不是最“骚”的。
Mapstruct
大家可能或多或少见过该框架,一会儿你看完就会发现,我擦 贼“骚”
我们代码实战演示一波:
maven 引入
//网上有的说,可以引入mapstruct-jdk1.8 我不建议引入,可能会出现未知的问题,一般引入下面两个就行。
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.0.Final</version>
</dependency>
复制代码
注意:这里引入的时候,尽量不要和Sweeager包进行冲突,该包中,也有mapstruct这个东西。大家注意下,如果遇到问题,大家可以下方留言 我们讨论下:
入门实战
比如我们DTO和BO (建议lombok)
@Data
public class Students {
private String name;
private int age;
private int sex; //0代表男 1:代表女
}
@Data
public class StudentsDTO {
private String name;
private int age;
private int sex; //0代表男 1:代表女
public StudentsDTO(){}
//构造方法
public StudentsDTO(String name,int age,int sex){
this.name = name;
this.age = age;
this.sex = sex;
}
}
复制代码
定义Mapper,该类是接口类
import org.mapstruct.Mapper;
//所有的骚操作都在这个注解mapper上,一会儿细说原理
@Mapper(componentModel = "spring")
public interface StudentsMapper {
//dto 转 BO
Students DTOToStBo(StudentsDTO st);
}
复制代码
componentModel = "spring" 代表通过生成spring bean注入使用,Mapper注解加上spring配置,会自动生成一个bean,直接使用bean注入即可访问。
@Mapper中描述映射,在编辑的时候mapstruct将会根据此描述生成实现类:
-
当属性与其目标实体副本同名时,它将被隐式映射。
-
当目标实体中的属性具有不同名称时,可以通过@Mapping注释指定其名称
对的,当属性名称不一致时,可以用@Mapping注释指定其名称
@Mapper(componentModel = "spring")
public interface StudentsMapper {
//比如学生类里面,班级属性不一样
@Mapping(source = "className", target = "grades")
Students DTOToStBo(StudentsDTO st);
}
复制代码
怎么调用呢。通过Mappers 工厂生成静态实例使用。(我前面讲过工厂模式和单例模式 大家可以点击头像查看)
import org.mapstruct.factory.Mappers
@Mapper(componentModel = "spring")
public interface StudentsMapper {
//静态实例化
StudentsMapper INSTANCE = Mappers.getMapper(StudentsMapper.class);
//比如学生类里面,班级属性不一样
@Mapping(source = "className", target = "grades")
Students DTOToStBo(StudentsDTO st);
}
复制代码
StudentsDTO stDTO = new StudentsDTO("张三",18,1);
Students students = StudentsMapper.INSTANCE.DTOToStBo(stDTO);
System.out.println("students.name -> "+students.getName());
//输出:张三
复制代码
OK 完成转换赋值,接下来我们大概说说原理
原理
首先说下注解,@Mapper,此注解是当在编译代码时,生成实现类impl,而真正实现对象数据赋值的就是在这个类里面。
当编译好之后 大家可以去target包下面看下:
或者直接这里进去实现类
看到没,原来核心是这里,贼“骚”。
上面我们用gatMapper 其原理是就是为了拿这个实现类
OK 今天就讲到这里。
弦外之音
感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。也欢迎有问题我们下面评论交流
加油! 我们下期再见! 下面是本月精讲的设计模式 欢迎阅读: