常用Bean拷贝工具一览

常用Bean拷贝工具一览

前言

对象拷贝,是我们在开发过程中,绕不开的过程,既存在于Po、Dto、Do、Vo各个表现层数据的转换,也存在于系统交互如序列化、反序列化。

1.深拷贝和浅拷贝浅拷贝的概念

Java对象拷贝分为深拷贝和浅拷贝, 深拷贝和浅拷贝是指在赋值一个对象时,拷贝的深度不同。

在进行深拷贝时,会拷贝所有的属性,并且如果这些属性是对象,也会对这些对象进行深拷贝,直到最底层的基本数据类型为止。这意味着,对于深拷贝后的对象,即使原对象的属性值发生了变化,深拷贝后的对象的属性值也不会受到影响。

2.深拷贝和浅拷贝的区别

​ 对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容称为深拷贝。

  • 浅拷贝仅仅复制对象的引用,而不是对象本身,新旧对象共享同一块内存
  • 深拷贝会拷贝所有的属性,并且如果这些属性是对象,也会对这些对象进行深拷贝,直到最底层的基本数据类型为止。新对象和原对象没有任何关联,修改新对象不影响原对象
3. 赋值和拷贝的区别

区别1:赋值是指赋值者与被赋值者指向的是同一地址,只是标签不同,当a的内容被修改之后,b也发生改变;而拷贝是指把原来的值复制了一份存在另一个地址,当原地址的内容被改变之后,拷贝的内容不变。
区别2:赋值侧重于更新,构造侧重于构造。 等号不等于赋值,他可能是拷贝构造。

4. 工具类

目前常用的属性拷贝工具,包括Apache,Spring,的BeanUtils、Hutool的 BeanUtil 、Cglib的BeanCopier、mapstruct都是浅拷贝

介绍一下常用的工具类:

  • Apache BeanUtils

  • Spring BeanUtils

  • Hutool BeanUtil

  • cglib BeanCopier

  • Mapstruct

spring 和 apache,hutool使用的都是反射,cglib是基于字节码文件的操作.都是在都代码运行期间动态执行的;

Mapstruct不同,它在编译期间就生成了 Bean属性复制的代码,运行期间就无需使用反射或者字节码技术,

所以具有很高的性能。

Apache BeanUtils

BeanUtils是Apache commons组件里面的成员,由Apache提供的一套开源 api,用于简化对javaBean的操作,能够对基本类型自动转换。

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>
方法1. org.apache.commons.beanutils.BeanUtils
//dest目标文件,orig原始的、源文件
 public static void copyProperties(Object dest, Object orig) 
    throws IllegalAccessException, InvocationTargetException {
        BeanUtilsBean.getInstance().copyProperties(dest, orig);
    }
方法2. org.apache.commons.beanutils.PropertyUtils
 //dest目标文件,orig原始的、源文件
 public static void copyProperties(final Object dest, final Object orig)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        PropertyUtilsBean.getInstance().copyProperties(dest, orig);
    }
注意
  1. 方法1和的BeanUtils浅拷贝不对属性类型进行处理 不同类型无法复制,必须类型和名称一致
  2. spring和apache的copy属性的方法源和目的参数的位置正好相反,所以导包和调用的时候都要注意一下
  3. 方法3 PropertyUtils 支持不同类型同名称复制

Spring BeanUtils

spring框架下自带的工具,如果使用的spring项目时不需要单独引入依赖,单独使用时需要引入坐标:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

示例: A,B为对象 BeanUtils.copyProperties(A, B);

☆☆ A拷贝到B

org.springframework.beans.BeanUtils
//source 源文件,target 目标文件
public static void copyProperties(Object source, Object target) 
    throws BeansException {
		copyProperties(source, target, null, (String[]) null);
	}

BeanUtils不对属性类型进行处理 不同类型无法复制,必须类型和名称一致

spring和apache的copy属性的方法源和目的参数的位置正好相反,所以导包和调用的时候都要注意一下

注意

在阿里巴巴的开发手册中,强制避免使用apache BeanUtils进行拷贝,建议使用Spring BeanUtils或下面要介绍的BeanCopier。主要原因还是在于Spring并没有与 apache一样对反射做了过多校验,另外Spring BeanUtils内部使用了缓存,加快转换的速度。此外,由于我们的大多项目已经集成了Spring ,如果没有其他特殊的需求,直接使用它的BeanUtils就能满足我们的基本需求。

Hutool BeanUtil

hutool是个人平常使用比较频繁的一个工具包,对文件、加密解密、转码、正则、线程、XML等JDK方法进行封装,并且也可以进行对象的拷贝。在使用前引入坐标:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.1.0</version>
</dependency>
cn.hutool.core.bean.BeanUtil
	/**
	 * 复制Bean对象属性
	 *
	 * @param source 源Bean对象
	 * @param target 目标Bean对象
	 */
	public static void copyProperties(Object source, Object target) {
		copyProperties(source, target, CopyOptions.create());
	}

在这里插入图片描述

hutool的BeanUtil还提供了很多其他实用的方法:

在这里插入图片描述

注意

  1. 支持不同类型同名称复制

  2. 使用浅拷贝方式

  3. 个人在使用中感觉Bean和Map的互相转换还是很常用的,有时在使用Map接收参数时,后期能够很方便的把Map转换为Bean

cglib BeanCopier

cglib(Code Generation Library)是一个强大的、高性能、高质量的代码生成类库,BeanCopier依托于cglib的字节码增强能力,动态生成实现类,完成对象的拷贝。

如果工程内含有spring-core包的依赖,也不需要额外引入依赖,否则需要引入坐标:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
import org.springframework.cglib.beans.BeanCopier;
public static BeanCopier create(Class source, Class target, boolean useConverter) {
     Generator gen = new Generator();
     gen.setSource(source);
     gen.setTarget(target);
     gen.setUseConverter(useConverter);
     return gen.create();
 }

1.使用浅拷贝方式,类型不同或者包装类基本类之间无法正常拷贝

Mapstruct

官网 :https://mapstruct.org/documentation/stable/reference/html/

MapStruct 是一个代码生成器,它基于约定优于配置方法极大地简化了 Java bean 类型之间映射的实现。自动生成的映射转换代码只使用简单的方法调用,因此速度快、类型安全而且易于理解阅读,总的来说,有如下三个特点:

  1. 基于注解
  2. 在编译期自动生成映射转换代码
  3. 类型安全、高性能、无依赖性

使用Mapstruct需要需要引入下面的依赖:

<dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.4.2.Final</version>
        </dependency>

//需要额外写一个接口来实现
@Mapper(componentModel = "spring",
        unmappedTargetPolicy = ReportingPolicy.IGNORE,unmappedSourcePolicy =ReportingPolicy.IGNORE)
interface ConvertMapper {
    ConvertMapper INSTANCE = Mappers.getMapper(ConvertMapper.class);

    Bean2 B2CyB1(Bean1 bean1);

//     对象属性类型不同或名称不同 可如下配置
//-------------    
    // 单个书写
//    // 默认值
//    @Mapping(source = "resourceId", target = "resourceId", defaultValue = "可以指定默认值")
//    // 常量
//    @Mapping(source = "resourceType", target = "resourceType",constant = "100")
//    // 日期转换
    //   @Mapping(source = "dateTime1", target = "dateTime", dateFormat = "yyyy-MM-dd")
    
    // 批量写法 与上述二选一
    @Mappings({
            // Integer 到 String的转换
           // @Mapping(source = "id", target = "numId", numberFormat = "$#.00"),
            //@Mapping(source = "name", target = "userName"),
            //Date 转字符串
            @Mapping(source = "dateTime1", target = "dateTime", dateFormat = "yyyy/MM/dd",ignore = true),
            // 使用java可执行代码或函数
            @Mapping(target = "dateTime2", expression = "java(java.time.LocalDateTime.now().toString())")
    })
    Bean3 B3ToB1(Bean1 bean1);  
}
//-------------------------------------------------------------------------------

//这里的@Mapper注解不是用于mybatis的注解,而是org.mapstruct.Mapper。使用起来也非常简单:

ConvertMapper mapper = Mappers.getMapper(ConvertMapper.class);
Bean2  bean2=mapper.B2CyB1(bean1);
Bean3  bean3=mapper.B3ToB1(bean1);

查看编译后的target目录,编译时将我们定义的ConvertMapper 接口,生成了ConvertMapperImpl实现类,并实现了B2CyB1方法。看一下编译生成的文件:
在这里插入图片描述
在这里插入图片描述

可以看到方法中为每一个属性生成了set方法,并且对于引用对象,生成了一个新的对象,使用深拷贝的方式,所以修改之前的引用对象,这里的值也不会改变。并且,这种使用set/get的方式比使用反射的速度更快。

获取转换器(Mapper)的几种方式

获取转换器的方式根据 @Mapper 注解的 componentModel 属性不同而不同,支持以下四种不同的取值:

  1. default 默认方式,默认方式,使用工厂方式(Mappers.getMapper(Class) )来获取
  2. cdi 此时生成的映射器是一个应用程序范围的 CDI bean,使用 @Inject 注解来获取
  3. springSpring 的方式,可以通过 @Autowired 注解来获取,在 Spring 框架中推荐使用此方式
  4. jsr330 生成的映射器用 @javax.inject.Named@Singleton 注解,通过 @Inject 来获取

① 通过工厂方式获取

上文的示例中都是通过工厂方式获取的,也就是使用 MapStruct 提供的 Mappers.getMapper(Class<T> clazz) 方法来获取指定类型的 Mapper。然后在调用的时候就不需要反复创建对象了,方法的最终实现是通过我们定义接口的类加载器加载 MapStruct 生成的实现类(类名称规则为:接口名称 + Impl),然后调用该类的无参构造器创建对象。核心源码如下所示:

在这里插入图片描述

② 使用依赖注入方式获取

对于依赖注入(dependency injection),使用 Spring 框架开发的朋友们应该很熟悉了,工作中经常使用。MapStruct 也支持依赖注入的使用方式,并且官方也推荐使用依赖注入的方式获取。使用 Spring 依赖注入的方式只需要指定 @Mapper 注解的 componentModel = "spring" 即可,示例代码如下:

@Mapper(componentModel = "spring")
public interface SourceMapper {

  SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);

  @Mapping(source = "sourceName", target = "targetName")
  Target toTarget(Source source);

}

我们可以使用 @Autowired 获取的原因是 SourceMapper 接口的实现类已经被注册为容器中一个 Bean 了,通过如下生成的接口实现类的代码也可以看到,在类上自动加上了 @Component 注解。

最后还有两个注意事项:① 当两个转换对象的属性不一致时(比如 DoctorDTO 中不存在 Doctor 对象中的某个字段),编译时会出现警告提示。可以在@Mapping 注解中配置 ignore = true,或者当不一致字段比较多时,可以直接设置 @Mapper 注解的 unmappedTargetPolicy 属性或unmappedSourcePolicy 属性设置为 ReportingPolicy.IGNORE。② 如果你项目中也使用了 Lombok,需要注意一下 Lombok 的版本至少是 1.18.10 或者以上才行,否则会出现编译失败的情况。

性能对比

更多数据请查看网址----- 无惧性能烦恼-12款Bean拷贝工具压测大比拼

img

拷贝选型

经过以上分析,随着数据量的增大,耗时整体呈上升趋势

  • 整体情况下,Apache BeanUtils的性能最差,日常使用过程中不建议使用
  • 在数据规模不大的情况下,spring、cglib、mapstruct差异不大,spring框架下建议使用spring的beanUtils,不需要额外引入依赖包
  • 数据量大的情况下,建议使用cglib和mapstruct
  • 涉及大量数据转换,属性映射,格式转换的,建议使用mapstruct

工具类

1.基于动态代理 BeanCopier 工具类
package com.tuling;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.cglib.beans.BeanCopier;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * menu:BeanCopier cglib beanCooy类  同名不同类型copy无效
 *
 * @author: superMa
 * @Version 1.0
 * @date:2023/9/14
 */
public class BeanUtilsCopy {


    /**
     * BeanCopier的缓存,避免频繁创建,高效复用
     */
    private static final ConcurrentHashMap<String, BeanCopier> BEAN_COPIER_MAP_CACHE = new ConcurrentHashMap<String, BeanCopier>();	

    /**
     * BeanCopier的copyBean,高性能推荐使用,增加缓存
     *
     * @param source 源文件的
     * @param target 目标文件
     */
    public static void copyBean(Object source, Object target) {
        String key = genKey(source.getClass(), target.getClass());
        BeanCopier beanCopier;
        if (BEAN_COPIER_MAP_CACHE.containsKey(key)) {
            beanCopier = BEAN_COPIER_MAP_CACHE.get(key);
        } else {
            beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
            BEAN_COPIER_MAP_CACHE.put(key, beanCopier);
        }
        beanCopier.copy(source, target, null);
    }

    /**
     * 不同类型对象数据copylist
     *
     * @param sourceList
     * @param targetClass
     * @param <T>
     * @return
     */
    public static <T> List<T> copyListProperties(List<?> sourceList, Class<T> targetClass) throws Exception {
        if (CollectionUtils.isNotEmpty(sourceList)) {
            List<T> list = new ArrayList<T>(sourceList.size());
            for (Object source : sourceList) {
                T target = copyProperties(source, targetClass);
                list.add(target);
            }
            return list;
        }
        return new ArrayList<T>();
    }

    /**
     * 返回不同类型对象数据copy,使用此方法需注意不能覆盖默认的无参构造方法
     * 
     * @param source
     * @param targetClass
     * @param <T>
     * @return
     */
    public static <T> T copyProperties(Object source, Class<T> targetClass) throws Exception {
        T target = targetClass.newInstance();
        copyBean(source, target);
        return target;
    }

    /**
     * @param srcClazz 源class
     * @param tgtClazz 目标class
     * @return string
     */
    private static String genKey(Class<?> srcClazz, Class<?> tgtClazz) {
        return srcClazz.getName() + tgtClazz.getName();
    }
}


2.基于hutool_cglib
/*
*单对象基于class创建拷贝
*单对象基于对象创建拷贝
*列表对象基于class创建拷贝
*对象转Map
*map基于class拷贝到bean
*map基于对象拷贝到bean
*map拷贝到map
/**

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanCopyUtils {
 /**
     * 单对象基于class创建拷贝
     *
     * @param source 数据来源实体
     * @param desc   描述对象 转换后的对象
     * @return desc
     */
    public static <T, V> V copy(T source, Class<V> desc) {
        if (ObjectUtil.isNull(source)) {
            return null;
        }
        if (ObjectUtil.isNull(desc)) {
            return null;
        }
        final V target = ReflectUtil.newInstanceIfPossible(desc);
        return copy(source, target);
    }

    /**
     * 单对象基于对象创建拷贝
     *
     * @param source 数据来源实体
     * @param desc   转换后的对象
     * @return desc
     */
    public static <T, V> V copy(T source, V desc) {
        if (ObjectUtil.isNull(source)) {
            return null;
        }
        if (ObjectUtil.isNull(desc)) {
            return null;
        }
        BeanCopier beanCopier = BeanCopierCache.INSTANCE.get(source.getClass(), desc.getClass(), null);
        beanCopier.copy(source, desc, null);
        return desc;
    }

    /**
     * 列表对象基于class创建拷贝
     *
     * @param sourceList 数据来源实体列表
     * @param desc       描述对象 转换后的对象
     * @return desc
     */
    public static <T, V> List<V> copyList(List<T> sourceList, Class<V> desc) {
        if (ObjectUtil.isNull(sourceList)) {
            return null;
        }
        if (CollUtil.isEmpty(sourceList)) {
            return CollUtil.newArrayList();
        }
        return StreamUtils.toList(sourceList, source -> {
            V target = ReflectUtil.newInstanceIfPossible(desc);
            copy(source, target);
            return target;
        });
    }
    /**
     * bean拷贝到map
     *
     * @param bean 数据来源实体
     * @return map对象
     */
    public static <T> Map<String, Object> copyToMap(T bean) {
        if (ObjectUtil.isNull(bean)) {
            return null;
        }
        return BeanMap.create(bean);
    }
 /**
     * map拷贝到bean
     *
     * @param map       数据来源
     * @param beanClass bean类
     * @return bean对象
     */
    public static <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) {
        if (MapUtil.isEmpty(map)) {
            return null;
        }
        if (ObjectUtil.isNull(beanClass)) {
            return null;
        }
        T bean = ReflectUtil.newInstanceIfPossible(beanClass);
        return mapToBean(map, bean);
    }

    /**
     * map拷贝到bean
     *
     * @param map  数据来源
     * @param bean bean对象
     * @return bean对象
     */
    public static <T> T mapToBean(Map<String, Object> map, T bean) {
        if (MapUtil.isEmpty(map)) {
            return null;
        }
        if (ObjectUtil.isNull(bean)) {
            return null;
        }
        BeanMap.create(bean).putAll(map);
        return bean;
    }

    /**
     * map拷贝到map
     *
     * @param map   数据来源
     * @param clazz 返回的对象类型
     * @return map对象
     */
    public static <T, V> Map<String, V> mapToMap(Map<String, T> map, Class<V> clazz) {
        if (MapUtil.isEmpty(map)) {
            return null;
        }
        if (ObjectUtil.isNull(clazz)) {
            return null;
        }
        Map<String, V> copyMap = new LinkedHashMap<>(map.size());
        map.forEach((k, v) -> copyMap.put(k, copy(v, clazz)));
        return copyMap;
    }

    /**
     * BeanCopier属性缓存<br>
     * 缓存用于防止多次反射造成的性能问题
     */
    public enum BeanCopierCache {
        /**
         * BeanCopier属性缓存单例
         */
        INSTANCE;

        private final SimpleCache<String, BeanCopier> cache = new SimpleCache<>();

        /**
         * 获得类与转换器生成的key在{@link BeanCopier}的Map中对应的元素
         *
         * @param srcClass    源Bean的类
         * @param targetClass 目标Bean的类
         * @param converter   转换器
         * @return Map中对应的BeanCopier
         */
        public BeanCopier get(Class<?> srcClass, Class<?> targetClass, Converter converter) {
            final String key = genKey(srcClass, targetClass, converter);
            return cache.get(key, () -> BeanCopier.create(srcClass, targetClass, converter != null));
        }

        /**
         * 获得类与转换器生成的key
         *
         * @param srcClass    源Bean的类
         * @param targetClass 目标Bean的类
         * @param converter   转换器
         * @return 属性名和Map映射的key
         */
        private String genKey(Class<?> srcClass, Class<?> targetClass, Converter converter) {
            final StringBuilder key = StrUtil.builder()
                .append(srcClass.getName()).append('#').append(targetClass.getName());
            if (null != converter) {
                key.append('#').append(converter.getClass().getName());
            }
            return key.toString();
        }
    }
}


3.基于 spring BeanUtils 的 工具类
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
 
 
/**
 * 对象拷贝工具
 * @author DELL
 * @version 1.0
 */
public class BeanObjectCopyUtils{
 
   private static  Logger LOGGER = LoggerFactory.getLogger( BeanObjectCopyUtils.class );
 
 
   /**
    * 拷贝对象
    * @param desObj 目标对象
    * @param origObj 源对象
    * @return  desObj 目标对象
    */
   public static <T,E> T copyObject(T desObj, E origObj){
      if(origObj!=null && desObj!=null){
         try {
            BeanUtils.copyProperties(origObj, desObj);
         }
         catch (Exception e) {
            LOGGER.error("object copy error",e);
            throw new RuntimeException("object copy error",e);
         }
      }
      return desObj;
   }
 
 
   /**
    * 拷贝List对象到另一个list对象
    * @param desClass  源List对象
    * @param sourceList 目标List对象
    * @return  List
    */
   @SuppressWarnings({ "rawtypes", "unchecked" })
   public static <T> List<T> copyListObjToListObj(Class<T> desClass, List sourceList){
      List desList=new ArrayList();
      if(sourceList!=null){
         for(int i=0; i<sourceList.size(); i++){
            try {
               Object  sourceObj = sourceList.get(i);
               Object  desObj = desClass.newInstance();
               BeanUtils.copyProperties(sourceObj, desObj);
               desList.add(desObj);
            }
            catch (Exception e) {
               LOGGER.error("list copy error",e);
               throw new RuntimeException("list copy error",e);
            }
 
         }
      }
      return desList;
   }

4.基于 huttol 的 工具类
package com.tuling;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;

import java.util.ArrayList;
import java.util.List;

/**
 * menu: java 实现 实体类之间的转换工具类
 *
 * @author: superMa
 * @Version 1.0
 * @date:2023/9/8
 */
public class BeanUtils {

    /**
     * 实体类集合转化
     *
     * @param objs
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> List<T> copy(List<?> objs, Class<T> clazz) {
        List<T> list = new ArrayList<>();
        if (objs.isEmpty()) {
            return list;
        }
        for (Object source : objs) {
            //把源对象类型强制转换为目标对象
            T target = JSON.parseObject(JSON.toJSONString(source), clazz);
            //把源对象属性赋值给目标对象(忽略大小写)
            BeanUtil.copyProperties(source, target,true);
            list.add(target);
        }
        return list;
    }

    /**
     * 实体类转化
     *
     * @param obj
     * @param clazz
     * @return
     */
    public static <T> T copyEntity(Object obj, Class<T> clazz) {
        if (obj == null) {
            return null;
        }
        //把源对象类型强制转换为目标对象
        T target = JSON.parseObject(JSON.toJSONString(obj), clazz);
        //把源对象属性赋值给目标对象
        BeanUtil.copyProperties(obj, target);
        //BeanUtil.copyProperties(obj, target,true);
        return target;
    }

}



  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值