SpringBoot如何进行对象复制的实践

目录
  • 为什么需要对象复制
  • 对象复制工具类推荐
  • Orika基本使用
    • 引入依赖
  • Orika工具类使用文档
    • TC1,基础实体映射
    • TC2,实体映射 - 字段转换
    • TC3,基础集合映射
    • TC4,集合映射 - 字段映射
    • TC5,集合与实体映射
    • TC6,类类型映射
    • TC7,多重映射
    • TC8,MyBaits plus分页映射
  • 小结

首先我们看看为什么需要对象复制?

为什么需要对象复制

如上,是我们平时开发中最常见的三层MVC架构模型,编辑操作时Controller层接收到前端传来的DTO对象,在Service层需要将DTO转换成DO,然后在数据库中保存。查询操作时Service层查询到DO对象后需要将DO对象转换成VO对象,然后通过Controller层返回给前端进行渲染。

这中间会涉及到大量的对象转换,很明显我们不能直接使用getter/setter复制对象属性,这看上去太low了。想象一下你业务逻辑中充斥着大量的getter&setter,代码评审时老鸟们会如何笑话你?

所以我们必须要找一个第三方工具来帮我们实现对象转换。

看到这里有同学可能会问,为什么不能前后端都统一使用DO对象呢?这样就不存在对象转换呀?

设想一下如果我们不想定义 DTO 和 VO,直接将 DO 用到数据访问层、服务层、控制层和外部访问接口上。此时该表删除或则修改一个字段,DO 必须同步修改,这种修改将会影响到各层,这并不符合高内聚低耦合的原则。通过定义不同的 DTO 可以控制对不同系统暴露不同的属性,通过属性映射还可以实现具体的字段名称的隐藏。不同业务使用不同的模型,当一个业务发生变更需要修改字段时,不需要考虑对其它业务的影响,如果使用同一个对象则可能因为 “不敢乱改” 而产生很多不优雅的兼容性行为。

对象复制工具类推荐

对象复制的类库工具有很多,除了常见的Apache的BeanUtils,Spring的BeanUtilsCglib BeanCopier,还有重量级组件MapStructOrikaDozerModelMapper等。

如果没有特殊要求,这些工具类都可以直接使用,除了Apache的BeanUtils。原因在于Apache BeanUtils底层源码为了追求完美,加了过多的包装,使用了很多反射,做了很多校验,所以导致性能较差,并在阿里巴巴开发手册上强制规定避免使用 Apache BeanUtils

至于剩下的重量级组件,综合考虑其性能还有使用的易用性,我这里更推荐使用Orika。Orika底层采用了javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件,在速度上比使用反射进行赋值会快很多。

国外大神 baeldung 已经对常见的组件性能进行过详细测试,大家可以通过 https://www.baeldung.com/java-performance-mapping-frameworks 查看。

Orika基本使用

要使用Orika很简单,只需要简单四步:

引入依赖

<dependency>
  <groupId>ma.glasnost.orika</groupId>
  <artifactId>orika-core</artifactId>
  <version>1.5.4</version>
</dependency>

构造一个MapperFactory

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

注册字段映射

mapperFactory.classMap(SourceClass.class, TargetClass.class)
   .field("firstName", "givenName")
   .field("lastName", "sirName")
   .byDefault()
   .register();

当字段名在两个实体不一致时可以通过.field()方法进行映射,如果字段名都一样则可省略,byDefault()方法用于注册名称相同的属性,如果不希望某个字段参与映射,可以使用exclude方法。

进行映射

MapperFacade mapper = mapperFactory.getMapperFacade();

SourceClass source = new SourceClass();
// set some field values
...
// map the fields of 'source' onto a new instance of PersonDest
TargetClass target = mapper.map(source, TargetClass.class);

经过上面四步我们就完成了SourceClass到TargetClass的转换。至于Orika的其他使用方法大家可以参考 http://orika-mapper.github.io/orika-docs/index.html

看到这里,肯定有粉丝会说:你这推荐的啥玩意呀,这个Orika使用也不简单呀,每次都要这先创建MapperFactory,建立字段映射关系,才能进行映射转换。

别急,我这里给你准备了一个工具类OrikaUtils,你可以通过文末github仓库获取。

它提供了五个公共方法:

分别对应:

  • 字段一致实体转换
  • 字段不一致实体转换(需要字段映射)
  • 字段一致集合转换
  • 字段不一致集合转换(需要字段映射)
  • 字段属性转换注册

接下来我们通过单元测试案例重点介绍此工具类的使用。

Orika工具类使用文档

先准备两个基础实体类,Student,Teacher。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String id;
    private String name;
    private String email;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private String id;
    private String name;
    private String emailAddress;
}

TC1,基础实体映射

/**
 * 只拷贝相同的属性
 */
@Test
public void convertObject(){
  Student student = new Student("1","javadaily","jianzh5@163.com");
  Teacher teacher = OrikaUtils.convert(student, Teacher.class);
  System.out.println(teacher);
}

输出结果:

Teacher(id=1, name=javadaily, emailAddress=null)

此时由于属性名不一致,无法映射字段email。

TC2,实体映射 - 字段转换

/**
 * 拷贝不同属性
 */
@Test
public void convertRefObject(){
  Student student = new Student("1","javadaily","jianzh5@163.com");

  Map<String,String> refMap = new HashMap<>(1);
  //map key 放置 源属性,value 放置 目标属性
  refMap.put("email","emailAddress");
  Teacher teacher = OrikaUtils.convert(student, Teacher.class, refMap);
  System.out.println(teacher);
}

输出结果:

Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com)

此时由于对字段做了映射,可以将email映射到emailAddress。注意这里的refMap中key放置的是源实体的属性,而value放置的是目标实体的属性,不要弄反了。

TC3,基础集合映射

/**
  * 只拷贝相同的属性集合
  */
@Test
public void convertList(){
  Student student1 = new Student("1","javadaily","jianzh5@163.com");
  Student student2 = new Student("2","JAVA日知录","jianzh5@xxx.com");
  List<Student> studentList = Lists.newArrayList(student1,student2);

  List<Teacher> teacherList = OrikaUtils.convertList(studentList, Teacher.class);

  System.out.println(teacherList);
}

输出结果:

[Teacher(id=1, name=javadaily, emailAddress=null), Teacher(id=2, name=JAVA日知录, emailAddress=null)]

此时由于属性名不一致,集合中无法映射字段email。

TC4,集合映射 - 字段映射

/**
 * 映射不同属性的集合
 */
@Test
public void convertRefList(){
  Student student1 = new Student("1","javadaily","jianzh5@163.com");
  Student student2 = new Student("2","JAVA日知录","jianzh5@xxx.com");
  List<Student> studentList = Lists.newArrayList(student1,student2);

  Map<String,String> refMap = new HashMap<>(2);
  //map key 放置 源属性,value 放置 目标属性
  refMap.put("email","emailAddress");

  List<Teacher> teacherList = OrikaUtils.convertList(studentList, Teacher.class,refMap);

  System.out.println(teacherList);
}

输出结果:

[Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com), Teacher(id=2, name=JAVA日知录, emailAddress=jianzh5@xxx.com)]

也可以通过这样映射:

Map<String,String> refMap = new HashMap<>(2);
refMap.put("email","emailAddress");
List<Teacher> teacherList = OrikaUtils.classMap(Student.class,Teacher.class,refMap)
        .mapAsList(studentList,Teacher.class);

TC5,集合与实体映射

有时候我们需要将集合数据映射到实体中,如Person类

@Data
public class Person {
    private List<String> nameParts;
}

现在需要将Person类nameParts的值映射到Student中,可以这样做

/**
 * 数组和List的映射
 */
@Test
public void convertListObject(){
   Person person = new Person();
   person.setNameParts(Lists.newArrayList("1","javadaily","jianzh5@163.com"));

    Map<String,String> refMap = new HashMap<>(2);
    //map key 放置 源属性,value 放置 目标属性
    refMap.put("nameParts[0]","id");
    refMap.put("nameParts[1]","name");
    refMap.put("nameParts[2]","email");

    Student student = OrikaUtils.convert(person, Student.class,refMap);
    System.out.println(student);
}

输出结果:

Student(id=1, name=javadaily, email=jianzh5@163.com)

TC6,类类型映射

有时候我们需要类类型对象映射,如BasicPerson类

@Data
public class BasicPerson {
    private Student student;
}

现在需要将BasicPerson映射到Teacher

/**
 * 类类型映射
 */
@Test
public void convertClassObject(){
    BasicPerson basicPerson = new BasicPerson();
    Student student = new Student("1","javadaily","jianzh5@163.com");
    basicPerson.setStudent(student);

    Map<String,String> refMap = new HashMap<>(2);
    //map key 放置 源属性,value 放置 目标属性
    refMap.put("student.id","id");
    refMap.put("student.name","name");
    refMap.put("student.email","emailAddress");

    Teacher teacher = OrikaUtils.convert(basicPerson, Teacher.class,refMap);
    System.out.println(teacher);
}

输出结果:

Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com)

TC7,多重映射

有时候我们会遇到多重映射,如将StudentGrade映射到TeacherGrade

@Data
public class StudentGrade {
    private String studentGradeName;
    private List<Student> studentList;
}

@Data
public class TeacherGrade {
    private String teacherGradeName;
    private List<Teacher> teacherList;
}

这种场景稍微复杂,Student与Teacher的属性有email字段不相同,需要做转换映射;StudentGrade与TeacherGrade中的属性也需要映射。

/**
 * 一对多映射
 */
@Test
public void convertComplexObject(){
  Student student1 = new Student("1","javadaily","jianzh5@163.com");
  Student student2 = new Student("2","JAVA日知录","jianzh5@xxx.com");
  List<Student> studentList = Lists.newArrayList(student1,student2);

  StudentGrade studentGrade = new StudentGrade();
  studentGrade.setStudentGradeName("硕士");
  studentGrade.setStudentList(studentList);

  Map<String,String> refMap1 = new HashMap<>(1);
  //map key 放置 源属性,value 放置 目标属性
  refMap1.put("email","emailAddress");
  OrikaUtils.register(Student.class,Teacher.class,refMap1);

  Map<String,String> refMap2 = new HashMap<>(2);
  //map key 放置 源属性,value 放置 目标属性
  refMap2.put("studentGradeName", "teacherGradeName");
  refMap2.put("studentList", "teacherList");

  TeacherGrade teacherGrade = OrikaUtils.convert(studentGrade,TeacherGrade.class,refMap2);
  System.out.println(teacherGrade);
}

多重映射的场景需要根据情况调用OrikaUtils.register()注册字段映射。

输出结果:

TeacherGrade(teacherGradeName=硕士, teacherList=[Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com), Teacher(id=2, name=JAVA日知录, emailAddress=jianzh5@xxx.com)])

TC8,MyBaits plus分页映射

如果你使用的是mybatis的分页组件,可以这样转换

public IPage<UserDTO> selectPage(UserDTO userDTO, Integer pageNo, Integer pageSize) {
  Page page = new Page<>(pageNo, pageSize);
  LambdaQueryWrapper<User> query = new LambdaQueryWrapper();
  if (StringUtils.isNotBlank(userDTO.getName())) {
    query.like(User::getKindName,userDTO.getName());
  }
  IPage<User> pageList = page(page,query);
  // 实体转换 SysKind转化为SysKindDto
  Map<String,String> refMap = new HashMap<>(3);
  refMap.put("kindName","name");
  refMap.put("createBy","createUserName");
  refMap.put("createTime","createDate");
  return pageList.convert(item -> OrikaUtils.convert(item, UserDTO.class, refMap));
}

小结

在MVC架构中肯定少不了需要用到对象复制,属性转换的功能,借用Orika组件,可以很简单实现这些功能。本文在Orika的基础上封装了工具类,进一步简化了Orika的操作,希望对各位有所帮助。

到此这篇关于SpringBoot如何进行对象复制的实践的文章就介绍到这了,更多相关SpringBoot 对象复制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解SpringBoot中Controller接收对象列表实现

    如果Spring Boot中对应的Controller要接收一个对象,该对象中又存放了一个List列表,那么页面该如何传递相关应的参数信息呢. 本篇文章给大家一个简单的示例,提供一种实现方式. 实体类 首先看实体类的结构(注意使用了Lombok): @Data public class Rules { private List<Rule> rules; } 对应Rule实体类代码如下: @Data public class Rule { /** * 类名 */ private String c

  • SpringBoot集成Redis,并自定义对象序列化操作

    SpringBoot项目使用redis非常简单,pom里面引入redis的场景启动器,在启动类上加@EnableCaching注解,项目启动会自动匹配上redis,这样项目中就可以愉快地使用了, 使用方法:要么使用@Cacheable一类的注解自动缓存,要么使用RedisTemplate手动缓存. (前提是你的本机或者是远程主机要先搭好redis环境) 虽然SpringBoot好用,但这里也有好多坑,SpringBoot和MySQL一样,易学难精,阳哥说的对,练武不练功,到老一场空. 下面,我将

  • Spring Boot中单例类实现对象的注入方式

    Spring Boot 单例类实现对象的注入 1.最近接手了一个项目 项目用的是SpringBoot,但其中有个类用的是单例,为了不改变单例,且还需要引入Spring管理的Bean对象 2.对于一个单例类按照平时的注解方式添加 启动时会报空指针异常,因为static类对象是创建对象后,内存中还没有注入Bean信息,且无法初始化Bean实例,这里的解决办法是利用@PostConstruct来对单例类中对象的注入 @Component public class MesssageHandle impl

  • IPSec VPN与SSL VPN技术对比

    本文就IPSec VPN与SSL VPN技术进行详细的比较,以便各界用户能够更好的了解VPN的技术,选择合适的VPN产品. 有别于传统VPN的另外一种VPN实现技术是采用基于SSL的VPN.这种基于SSL的VPN提供了与IPSec VPN近似的安全性.由于讨论IPSec VPN的文章比较多,所以笔者在此就不再赘述,只是阐述SSL VPN的工作原理和两种技术的对比. SSL VPN如何工作 SSL VPN一般的实现方式是在企业的防火墙后面放置一个SSL代理服务器.如果用户希望安全地连接到公司网络上

  • 使用spring boot开发时java对象和Json对象转换的问题

    将java对象转换为json对象,市面上有很多第三方jar包,如下: jackson(最常用) <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind&l

  • springboot2.x解决运行顺序及Bean对象注入顺序的问题

    1 前言 通过指定接口,重写指定方法,可以在Bean对应的生命周期方法中执行相应的程序 2 测试 本文将分析几个Bean对象,为它们设置优先级(通过@Order),然后再打断点调试,测试各种生命周期方法的运行的顺序 在项目当中最让人头疼的就是bean对象不被注入的问题,通过本文,你可以很好的解决这个问题. 先看看本程序使用的依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="

  • SpringBoot整合阿里云OSS对象存储服务实现文件上传

    1. 准备工作: 一.首先登录阿里云OSS对象存储控制台创建一个Bucket作为你的存储空间. 二.创建Access Keyan按要求创建进行,这里的方法步骤我就不展现出来了,你们可以自行查询阿里云文档,这个获取值本身就不难. 重点:记下你的AccessKey ID.AccessKey Secret以及你刚才创建的Buacket名字BucketName. 2. 配置: 在pom里引入oss要用的依赖 <dependency> <groupId>com.aliyun.oss</

  • Springboot 如何获取上下文对象

    目录 Springboot上下文对象获取 或者更简单的写法: spring boot获取上下文 随时取出被spring管理的bean对象 方法一: 方式二: Springboot上下文对象获取 package it.benjamin.aspirinweb.mem; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.sp

  • SpringBoot如何进行对象复制的实践

    目录 为什么需要对象复制 对象复制工具类推荐 Orika基本使用 引入依赖 Orika工具类使用文档 TC1,基础实体映射 TC2,实体映射 - 字段转换 TC3,基础集合映射 TC4,集合映射 - 字段映射 TC5,集合与实体映射 TC6,类类型映射 TC7,多重映射 TC8,MyBaits plus分页映射 小结 首先我们看看为什么需要对象复制? 为什么需要对象复制 如上,是我们平时开发中最常见的三层MVC架构模型,编辑操作时Controller层接收到前端传来的DTO对象,在Service

  • SpringBoot工程中Spring Security应用实践记录流程分析

    目录 SpringSecurity 应用 简介 认证授权分析 SpringSecurity 架构设计 快速入门实践 创建项目 添加项目依赖 启动服务访问测试 自定义认证逻辑 认证流程分析 定义security配置类 定义数据访问层对象 定义UserDetailService接口实现类 自定义登陆页面 启动服务进行访问测试 授权逻辑设计及实现 修改授权配置类 定义资源访问对象 启动服务实现访问测试 总结(Summary) SpringSecurity 应用 简介 Spring Security是一

  • 深入理解JavaScript中的对象复制(Object Clone)

    JavaScript中并没有直接提供对象复制(Object Clone)的方法.因此下面的代码中改变对象b的时候,也就改变了对象a. a = {k1:1, k2:2, k3:3}; b = a; b.k2 = 4; 如果只想改变b而保持a不变,就需要对对象a进行复制. 用jQuery进行对象复制 在可以使用jQuery的情况下,jQuery自带的extend方法可以用来实现对象的复制. a = {k1:1, k2:2, k3:3}; b = {}; $.extend(b,a); 自定义clone

  • 深入java对象复制的分析

    java本身提供了对象复制的能力,在java.lang.Object类中有clone方法,该方法是一个protected方法,在子类需要重写此方法并声明为public类型,而且还需实现Cloneable接口才能提供对象复制的能力,clone()是一个native方法,native方法的效率一般来说都是远高于java中的非native方法,对性能比较关心的话首先考虑这种方式,这种复制在网上有很多例子就不多写了:在这要用的另一种方式--通过java的反射机制复制对象,这种方式效率可能会比clone(

  • php5对象复制、clone、浅复制与深复制实例详解

    本文实例讲述了php5对象复制.clone.浅复制与深复制.分享给大家供大家参考,具体如下: 对象复制的由来 为什么对象会有"复制"这个概念,这与PHP5中对象的传值方式是密切相关的,让我们看看下面这段简单的代码 PHP代码 /** * 电视机类 */ class Television { /** 屏幕高度 */ protected $_screenLength = 300; /** 屏幕宽度 */ protected $_screenHight = 200; /** 电视机外观颜色

  • JS对象复制(深拷贝和浅拷贝)

    一.浅拷贝 1.Object.assign(target,source,source...) a.可支持多个对象复制 b.如果source和target属性相同 source会复制target的属性 c.target只能为Object对象 var obj = {a:1,b:2} undefined Object.assign({c:3},obj) {c: 3, a: 1, b: 2} obj {a: 1, b: 2} 兼容性写法if(Object.assign){//兼容}else{//不兼容}

  • 深入理解Java对象复制

    一.图示 二.MapStruct pom文件 <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artif

  • springboot整合security和vue的实践

    目录 环境 1.security参考资料 认证流程原理: 2.springboot整合security要点 2.1获取登录用户信息 2.2自定义登入登出url 2.3自定义Handler返回json 2.4记住我功能 2.5验证码功能 2.6限制登录次数 2.7密码加密 2.8后台提供接口,返回前端json,整合vue做前端登入登出 3.测试 环境 springboot1.5.9 完整代码,内有sql,先建库,在运行sql建表,sql中已插入测试的数据. https://github.com/2

  • C#使用表达式树实现对象复制的示例代码

    需求背景:对象复制性能优化:同时,在对象复制时,应跳过引用类型的null值复制,值类型支持值类型向可空类型的复制 using Common; using System; class Program { static void Main(string[] args) { TestClassA classA = new TestClassA() { PropA = new TestClass() { Name = "cs1" }, PropB = "c1", PropC

  • SpringBoot返回Json对象报错(返回对象为空{})

    目录 1 需求描述 2 代码展示 3 原因分析 4 解决方案 5 效果展示 6 结束语 1 需求描述 我们现在要干一个什么事情呢,我们要在浏览器输入一个请求地址,然后我们的后端就给我返回一个User对象即可,并且我希望以Json的格式返回.这个需求很明确,我们先直观的展示一下效果.发送请求: 接受结果: 2 代码展示 行了,明确了需求我们开始整活儿.首先我们老规矩还是先展示一下目录结构(其中标红的文件使我们今天要用到的): 接下来是具体的文件内容首先呢我们展示一下User.java文件 pack

随机推荐