Java对象转换的方案分享

目录
  • 前言
  • 为什么模型要分这么多层?
  • 模型之间的转换
    • 建议不要用的方式
    • 常用的方式
  • 使用方式
    • 定义对象
  • BeanCopier
    • 最简单的使用方式
    • 创建可复用的BeanCopier工具类
    • MapStruct
    • 引入mapstruct
    • 简单Demo
    • 常见用法
  • 性能测试
    • 测试代码
    • 测试结果
  • 最后

前言

系统变的复杂,系统的层次划分越来越细,边界也越来越明确。 然后每一层之间一般都有自己要处理的领域对象,统称为pojo一般在model或者domain包下(类的后缀不能为pojo)。

常见的一些模型类型:

  • PO、DO:持久层对象,一般和数据库直接打交道。
  • DTO:数据传输对象,系统之间的交互,再服务层提供服务的时候输出到其它系统。
  • VO:视图对象,用于前端模型展示。 当然有时候前端也可以看做另外一个系统,使用DTO模型;
  • BO:业务逻辑对象,比较少用...

为什么模型要分这么多层?

在复杂一点的业务中,业务建模是非常有必要的,一定要抽象出业务上常用的领域模型,统一技术和非技术同学的语言。

建完模型之后,在技术的系统中,为了方便维护代码,分离关注点,也会进行再次分层,让每一层解决特定的问题。模型的分层是随着系统的分层而来的;试想所有的模型属性在一个对象中,这个对象你看的懂吗?

举个实际的案例:

  • 数据层一般用DO
  • 现在要透出数据给其他系统,DO中一般都会有创建人,创建时间,修改人,修改时间,当前对象所处的环境等信息; 但是外部的系统需要环境、创建人信息吗? 很多时候不需要,站在数据安全的角度,一般只透出必要的字段就可以; 这些要输出要外部系统的必要字段,一般定义在DTO中。
  • 到前端系统,前端系统展示上所需的逻辑和输出到外部系统的又有点不太一样,前端系统可能要创建人,创建时间,但是不要另外一些东西,或者一些敏感的配置不能透出给前端,这个时候一般也会再定义一个新的对象。

简单说就是当我们的系统要输出能力到外部系统的时候,不同系统要的数据不一样,数据安全要求我们不能透出这么多的数据,一定要做一层处理。 另外给另外一个系统关注的数据,而不是一股脑的全部都给对方,对方处理起来也方便。

模型之间的转换

建议不要用的方式

  • 手写get\set; 虽然性能高,但是费劲并且眼花缭乱,一不小心就写错了,难以维护,复用度不高
  • BeanUtils,apacha和spring包下都有对应的类,但是底层用到的都是反射,性能比较差,大流量的情况下一般不用
  • 直接fastjson,gc会很频繁,而且性能比较差

常用的方式

  • cglib的beanCopier,开销在创建BeanCopier,一般在创建类的时候提前创建好一个,在代码运行的时候直接进行copy,性能接近原生。
  • mapstruct 性能和原生代码一样,支持复杂的转化场景,实现原理同lombok编译的时候生成对应的代码。

以上从技术分类的角度来看:

  • 反射:fastjson,beanutil 都不建议用
  • get\set: beancoper通过字节码进行getset,mapstruct编译的时候生成getset。 性能相对较好。

使用方式

个人觉得,如果说对象比较简单的时候,使用BeanCopier就可以了,因为spring的aop依赖cglib,默认情况下就已经引入了对应的包了,不需要额外的依赖直接就可以用。

如果很复杂的模型之间的转换,并且对性能有更极致的要求,考虑使用下MapStruct。

定义对象

UserDO

@Data
public class UserDO {
  private Long id;
  private String name;
  private Integer gender;
  private String password;
  private Date gmtCreate;
  private Date gmtModified;
}

UserDTO

@Data
public class UserDTO {
  private Long id;
  private String name;
  private Integer gender;
}

BeanCopier

最简单的使用方式

BeanCopier beanCopier = BeanCopier.create(UserDO.class, UserDTO.class, false); bean.copy即可;

private static void simpleBeanCopy() {
    BeanCopier beanCopier = BeanCopier.create(UserDO.class, UserDTO.class, false);
    UserDO userDO = new UserDO();
    userDO.setId(1L);
    userDO.setName("aihe");
    userDO.setGmtCreate(new Date());
    userDO.setGender(0);
    userDO.setPassword("xxxxxx");
    UserDTO userDTO = new UserDTO();
    beanCopier.copy(userDO, userDTO,null);
    Assert.assertEquals("名称未成功拷贝",userDTO.getName(),"aihe");
    Assert.assertEquals("Id未成功拷贝", 1L, (long)userDTO.getId());
    Assert.assertEquals("性别未成功拷贝", Integer.valueOf(0),userDTO.getGender());
  }

创建可复用的BeanCopier工具类

package me.aihe.daka;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.cglib.beans.BeanCopier;
/**
 * @author : aihe
 * @date : 2022/9/12 9:21 AM
 * 使用场景:
 * 功能描述:
 */
public class BeanCopyUtils {

    /**
     * beanCopier缓存
     * 由sourceClass和targetClass可以确定一个唯一的BeanCoper,因此使用二级Map;
     */
    private static Map<Class<?>, Map<Class<?>, BeanCopier>> beanCopierMap = new ConcurrentHashMap<>();
    /**
     * 直接指定Bean对象进行拷贝
     * @param sourceBean
     * @param targetBean
     * @param <S>
     * @param <T>
     */
    public static <S,T> void copy(S sourceBean,T targetBean){
        @SuppressWarnings("unchecked")
        Class<S> sourceClass = (Class<S>) sourceBean.getClass();
        @SuppressWarnings("unchecked")
        Class<T> targetClass = (Class<T>) targetBean.getClass();

        BeanCopier beanCopier = getBeanCopier(sourceClass,targetClass);
        beanCopier.copy(sourceBean,targetBean,null);
    }
    /**
     * 转换方法
     * @param sourceBean 原对象
     * @param targetClass 目标类
     * @param <S>
     * @param <T>
     * @return
     */
    public static <S,T> T convert(S sourceBean,Class<T> targetClass){
        try {
            assert sourceBean!=null;
            T targetBean = targetClass.newInstance();
            copy(sourceBean,targetBean);
            return targetBean;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private static <S,T> BeanCopier getBeanCopier(Class<S> sourceClass, Class<T> targetClass ){
        Map<Class<?>,BeanCopier> map = beanCopierMap.get(sourceClass);
        if(map == null || map.isEmpty()){
            BeanCopier newBeanCopier = BeanCopier.create(sourceClass, targetClass, false);
            Map<Class<?>,BeanCopier> newMap = new ConcurrentHashMap<>();
            newMap.put(targetClass,newBeanCopier);
            beanCopierMap.put(sourceClass,newMap);
            return newBeanCopier;
        }
        BeanCopier beanCopier = map.get(targetClass);
        if(beanCopier == null){
            BeanCopier newBeanCopier = BeanCopier.create(sourceClass, targetClass, false);
            map.put(targetClass,newBeanCopier);

            return newBeanCopier;
        }
        return beanCopier;
    }
}

同上:

UserDO userDO = new UserDO();
    userDO.setId(1L);
    userDO.setName("aihe");
    userDO.setGmtCreate(new Date());
    userDO.setGender(0);
    userDO.setPassword("xxxxxx");
    UserDTO userDTO = new UserDTO();
    BeanCopyUtils.copy(userDO, userDTO);
    Assert.assertEquals("名称未成功拷贝",userDTO.getName(),"aihe");
    Assert.assertEquals("Id未成功拷贝", 1L, (long)userDTO.getId());
    Assert.assertEquals("性别未成功拷贝", Integer.valueOf(0),userDTO.getGender());

MapStruct

案例集:github.com/mapstruct/m…

引入mapstruct

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <org.mapstruct.version>1.5.2.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.20</org.projectlombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
            <!-- IntelliJ does not pick up the processor if it is not in the dependencies.
             There is already an open issue for IntelliJ see https://youtrack.jetbrains.com/issue/IDEA-150621
            -->
            <scope>provided</scope>
        </dependency>

         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

简单Demo

定义Mapper

@Mapper
public interface UserDTOMapper {

    UserDTOMapper MAPPER = Mappers.getMapper( UserDTOMapper.class );
    //@Mapping( source = "test", target = "testing" )
    //@Mapping( source = "test1", target = "testing2" )
    UserDTO toTarget( UserDO s );
}

使用:

public static void main( String[] args ) {
        //simpleDemo();
        UserDO userDO = new UserDO();
        userDO.setId(1L);
        userDO.setName("aihe");
        userDO.setGmtCreate(new Date());
        userDO.setGender(0);
        userDO.setPassword("xxxxxx");
        UserDTO userDTO = UserDTOMapper.MAPPER.toTarget(userDO);
        Assert.assertEquals("名称未成功拷贝",userDTO.getName(),"aihe");
        Assert.assertEquals("Id未成功拷贝", 1L, (long)userDTO.getId());
        Assert.assertEquals("性别未成功拷贝", Integer.valueOf(0),userDTO.getGender());
    }

常见用法

  • 属性类型相同,名称不同的时候,使用@Mapping注解指定source和target字段名称对应关系, 如果有多个这种属性,那就指定多个@Mapping注解。
  • 忽略某个字段,在@Mapping的时候,加上ignore = true
  • 转化日期格式,字符串到数字的格式,可以使用dateFormat,numberFormat
  • 如果有自定义转换的需求,写一个简单的Java类即可,然后在方法上打上Mapstruct的注解@Named,在在@Mapper(uses = 自定义的类),然后@Mapping中用上qualifiedByName。
@Mapping(target = "userNick1", source = "userNick")
@Mapping(target = "createTime", source = "createTime", dateFormat = "yyyy-MM-dd")
@Mapping(target = "age", source = "age", numberFormat = "#0.00")
@Mapping(target = "id", ignore = true)
@Mapping(target = "userVerified", defaultValue = "defaultValue-2")
UserDTO toTarget( UserDO s );

性能测试

测试代码

import java.util.Date;
import com.alibaba.fastjson.JSON;
import org.junit.Before;
import org.junit.Test;
/**
 * @author : aihe aihe.ah@alibaba-inc.com
 * @date : 2022/9/12 9:47 AM
 * 使用场景:
 * 功能描述:
 */
public class BenchDemoTest{

  /**
   * 转化对象
   */
  private UserDO userDO;

  /**
   * 转化次数
   */
  private final static int count = 1000000;
  @Before
  public void before() {
    userDO = new UserDO();
    userDO.setId(1L);
    userDO.setName("aihe");
    userDO.setGmtCreate(new Date());
    userDO.setGender(0);
    userDO.setPassword("xxxxxx");
  }
  @Test
  public void mapstruct() {
    long startTime = System.currentTimeMillis();
    for (int i = 1; i <=count; i++) {
      UserDTO userDTO = UserDTOMapper.MAPPER.toTarget(userDO);
    }
    System.out.println("mapstruct time" + (System.currentTimeMillis() - startTime));
  }
  @Test
  public void beanCopier() {
    long startTime = System.currentTimeMillis();
    for (int i = 1; i <= count; i++) {
      UserDTO targetBean = new UserDTO();
      BeanCopyUtils.copy(userDO, targetBean);
    }
    System.out.println("beanCopier time" + (System.currentTimeMillis() - startTime));
  }
  @Test
  public void springBeanUtils(){
    long startTime = System.currentTimeMillis();
    for (int i = 1; i <=count; i++) {
      UserDTO userDTO = new UserDTO();
      org.springframework.beans.BeanUtils.copyProperties(userDO, userDTO);
    }
    System.out.println("springBeanUtils time" + (System.currentTimeMillis() - startTime));
  }
  @Test
  public void fastjson() {
    long startTime = System.currentTimeMillis();
    for (int i = 1; i <= count; i++) {
      JSON.parseObject(JSON.toJSONString(userDO), UserDTO.class);
    }
    System.out.println("fastjson time" + (System.currentTimeMillis() - startTime));
  }
}

测试结果

  • 可以看出BeanCopier和MapStruct是远远超过其他转换方式的...
  • BeanCopier虽然快,但是比mapstruct还是有20倍的性能差距...

最后

总结下本文的内容:

  • 软件系统一般都会进行分层,领域模型也会随之进行分层,即每层都有自己关注的模型对象; 分层的主要原因是便于维护。
  • 模型之间的对象经常要互相转换,常用的转换实现有反射和get/set,反射的性能很差不建议使用
  • 然后写了基于get/set实现的beancopier和mapstruct使用方式,简单测试了下性能,mapstrcut优于其它各种对象转换方式。并且MapStrcut支持功能更加复杂的对象转换。 性能又好,功能又强大,所以可以考虑优先使用.

到此这篇关于Java对象转换的方案分享的文章就介绍到这了,更多相关Java对象转换内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java BeanMap实现Bean与Map的相互转换

    目录 bean转Map map转Bean beanMap实现以及高性能的原因 net.sf.cglib.beans.BeanMap用法 bean转Map @Data public class Student { private int id; private String name; private Integer age; } Student student = new Student(); BeanMap beanMap = BeanMap.create(student); 此时的beanM

  • Java如何将String转换成json对象或json数组

    目录 将String转换成json对象或json数组 字符串转json数组的解决 首先导入 net.sf.json.JSONArray和net.sf.json.JSONObject 两个jar 包 将String转换成json对象或json数组 这里的SmartProejctEquipmentMap 是我自定的一个实体类,可以自己定义转换. 注意:json字符串中键的名称要和实体类一致. @Test public void TestJsonObject() { String datajson =

  • 复杂JSON字符串转换为Java嵌套对象的实现

    目录 背景 方法 预备工作 构建对象模型 使用jackson 库解析 使用GSON解析 不含列表的嵌套对象 背景 实际开发中,常常需要将比较复杂的 JSON 字符串转换为对应的 Java 对象.这里记录下解决方案. 如下所示,是入侵事件检测得到的 JSON 串: [{"rule_id":"反弹shell","format_output":"进程 pname 反向连接到 %dest_ip%:%dest_port%","

  • Java将json对象转换为map键值对案例详解

    本文的目的是把json串转成map键值对存储,而且只存储叶节点的数据 比如json数据如下: {responseHeader:{status:0,QTime:0},spellcheck:{suggestions:{中国:{numFound:9,startOffset:0,endOffset:2,suggestion:[中国工商银行, 中国人民, 中国国际, 中国农业, 中国市场, 中国经济, 中国人, 中国广播, 中国文化]}},collations:{collation:中国工商银行}}} 如

  • Java对象和Json文本转换工具类的实现

    目录 安装 下载源码 编译源码 添加依赖 Java对象 基本数据类型 数组 列表 字典 类 Java对象转换为Json文本 基本数据类型 数组 列表 字典 类 Json文本转换为Java对象 基本数据类型 数组 列表 字典 类 Json 是一个用于 Java 对象 和 Json 文本 相互转换的工具类. 安装 下载源码 git clone https://github.com/njdi/durian.git 编译源码 cd durian/ 切换至最新版本(Tag),如:0.4, git chec

  • sqlserver和java将resultSet中的记录转换为学生对象

    目录 1.Student.java 2.DBUtil.java 3.result.java 4.实现结果 要将结果转化为对象,所以第一步要创建一个对象: 1.Student.java public class Student { //学号.姓名.班级.性别.专业.学院 //类中的属性一定要跟数据库中的一摸一样 包括名称,数据类型 private String 学号; private String 姓名; private String 班级; private String 性别; private

  • 如何将Java对象转换为JSON实例详解

    要将 Java 对象或 POJO (普通旧 Java 对象)转换为 JSON,我们可以使用JSONObject将对象作为参数的构造函数之一.在下面的示例中,我们将StudentPOJO 转换为 JSON 字符串.Student类必须提供 getter 方法,JSONObject通过调用这些方法创建 JSON 字符串. 在此代码段中,我们执行以下操作: 使用 setter 方法创建Student对象并设置其属性. 创建JSONObject调用object并将Student对象用作其构造函数的参数.

  • Java对象转换的方案分享

    目录 前言 为什么模型要分这么多层? 模型之间的转换 建议不要用的方式 常用的方式 使用方式 定义对象 BeanCopier 最简单的使用方式 创建可复用的BeanCopier工具类 MapStruct 引入mapstruct 简单Demo 常见用法 性能测试 测试代码 测试结果 最后 前言 系统变的复杂,系统的层次划分越来越细,边界也越来越明确. 然后每一层之间一般都有自己要处理的领域对象,统称为pojo一般在model或者domain包下(类的后缀不能为pojo). 常见的一些模型类型: P

  • 详解Java对象转换神器MapStruct库的使用

    目录 前言 MapStruct简介 MapStruct入门 1. 引入依赖 2. 需要转换的对象 3. 创建转换器 4. 验证 5. 自动生成的实现类 MapStruct进阶 场景1:属性名称不同.(基本)类型不同 场景2:统一映射不同类型 场景3:固定值.忽略某个属性.时间转字符串格式 场景4:为某个属性指定转换方法 场景5:多个参数合并为一个对象 场景6:已有目标对象,将源对象属性覆盖到目标对象 场景7:源对象两个属性合并为一个属性 小结 前言 在我们日常开发的程序中,为了各层之间解耦,一般

  • java对象转换String类型的三种方法

    一.采用Object.toString()toString方法是java.lang.Object对象的一个public方法.在java中任何对象都会继承Object对象,所以一般来说任何对象都可以调用toString这个方法.这是采用该种方法时,常派生类会覆盖Object里的toString()方法.但是在使用该方法时要注意,必须保证Object不是null值,否则将抛出NullPointerException异常. 二.采用(String)Object 该方法是一个标准的类型转换的方法,可以将

  • java对象与json对象之间互相转换实现方法示例

    本文实例讲述了java对象与json对象之间互相转换实现方法.分享给大家供大家参考,具体如下: import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import net.sf.json.JSONArray; import net.sf.json.JSONObject; public class MainClass { public st

  • JAVA对象JSON数据互相转换的四种常见情况

    1. 把java 对象列表转换为json对象数组,并转为字符串 复制代码 代码如下: JSONArray array = JSONArray.fromObject(userlist);     String jsonstr = array.toString(); 2.把java对象转换成json对象,并转化为字符串 复制代码 代码如下: JSONObject object = JSONObject.fromObject(invite);    String str=object.toString

  • java对象和json的来回转换知识点总结

    为了是java中的对象便于理解,我们可以使用一款比较好用的数据格式,在数据解析的时候也会经常用到,它就是JSON.在这里我们转换对象和字符串时,需要java先变成json对象的模式.为了防止有人对JSON数组和对象的概念混淆,我们会先对这两个概念理解,然后带来java对象和json的来回转换的方法. 1.JSON数组和对象的区别 JSONArray是将数据转换为数组形式: strArray:[{"address":"北京市西城区","age":&

  • Java 对象序列化 NIO NIO2详细介绍及解析

    Java 对象序列化 NIO NIO2详细介绍及解析 概要: 对象序列化 对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而可以保存到磁盘或者进行网络传输,其它程序获得这个二进制流后可以将其恢复成原来的Java对象. 序列化机制可以使对象可以脱离程序的运行而对立存在 序列化的含义和意义 序列化 序列化机制可以使对象可以脱离程序的运行而对立存在 序列化(Serialize)指将一个java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢

  • Java对象序列化操作详解

    本文实例讲述了Java对象序列化操作.分享给大家供大家参考,具体如下: 当两个进程在进行远程通信时,彼此可以发送各种类型的数据.无论是何种类型的数据,都会以二进制序列的形式在网络上传送.发送方需要把这个Java对象转换为字节序列,才能在网络上传送:接收方则需要把字节序列再恢复为Java对象. 只能将支持 java.io.Serializable 接口的对象写入流中.每个 serializable 对象的类都被编码,编码内容包括类名和类签名.对象的字段值和数组值,以及从初始对象中引用的其他所有对象

  • java对象序列化操作实例分析

    本文实例讲述了java对象序列化操作.分享给大家供大家参考,具体如下: 在java中可以将对象进行序列化操作 要使对象能够被序列化,那么被序列化的对象要实现接口Serializable,此接口位于java.io包中 pakacge demo; import java.io.Serializable; /** * 实现了Serializable 接口的demo类 */ public class Demo1 implements Serializable { private String name;

随机推荐