7.9 SpringBoot实战 拷贝工具类,扩展BeanUtils.copyProperties

news2025/2/22 2:42:22

CSDN成就一亿技术人

文章目录

  • 前言
  • 一、拷贝普通对象Bean
    • 1.1 基础的Bean拷贝
    • 1.2 支持忽略某些属性
    • 1.3 支持忽略字段值为null的属性
    • 1.4 通用的Bean拷贝
      • 1.4.1 拷贝时可指定忽略属性
      • 1.4.2 拷贝时外加忽略null属性
  • 二、拷贝集合对象List
    • 2.1 拷贝时可指定忽略属性
    • 2.2 拷贝时外加忽略null属性
  • 三、拷贝分页对象Page
  • 四、整个CopyUtils类源码
  • 五、应用到现有代码
  • 六、测试+Git提交
  • 最后


前言

为什么要实现拷贝工具类?

本文主要解决在VO、BO、PO之间对象转换时的代码冗余问题,扩展BeanUtils,提升我们使用BeanUtils.copyProperties的用户体验。

本文主要实现3种常见场景的拷贝:拷贝普通对象Bean拷贝集合对象List拷贝分页对象Page,内容不多但都是项目必备,并且涵盖了几个关键的基础知识:泛型方法、函数式接口、可变参数、重载!最关键的是有完整的拷贝工具类源码~ 还等什么,Let’s Go~


一、拷贝普通对象Bean

例如,我们将PO对象(student)转换成BO对象(studentBO),前面的代码是这样写的:

StudentBO studentBO = new StudentBO();
BeanUtils.copyProperties(student, studentBO);

需要先new出来BO,再通过BeanUtils.copyProperties将PO属性拷贝到BO,是不是有点繁琐?能不能一步到位?当然!!!

1.1 基础的Bean拷贝

只需要定义一个泛型方法,稍微封装即可!

public static <S, T> T copy(S source, Supplier<T> target) {
    if (source == null) {
        return null;
    }
    T t = target.get();
    copyProperties(source, t);
    return t;
}

这里顺便提一下泛型方法 基本语法

public <类型参数> 返回类型 方法名(类型参数 变量名) {
    ...
}

对应copy方法定义一一解读:

在这里插入图片描述

  • <S,T>是类型参数,这里是用到两个泛型参数,多个以逗号分隔:S代表源对象类型,T代表目标对象类型
  • 之后的T 是返回类型,即最后return 的对象类型必须是T类型,这里是通过Supplier<T>由调用方提供返回对象
  • S source 是定义参数source,它的类型是S类型

那么最终调用就变成了:

StudentBO studentBO = CopyUtils.copy(student, StudentBO::new);

一行代码搞定,并且通用!!!

1.2 支持忽略某些属性

在某些场景下,我们在拷贝的时侯会忽略某些属性,刚好这也是BeanUtils.copyProperties支持的重载,所以,我们也需要只增加一个可变参数:ignoreProperties,就完成了重载增强:

public static <S, T> T copy(S source, Supplier<T> target, String... ignoreProperties) {
    if (source == null) {
        return null;
    }
    T t = target.get();
    copyProperties(source, t, ignoreProperties);
    return t;
}

…是可变参数,像上面这样定义,可以传入任意个String参数,或一个String[]数组,也可以不传入参数!

例如,在拷贝时我想忽略verifyTime属性,就可以这样调用:

StudentBO studentBO = CopyUtils.copy(student, StudentBO::new, "verifyTime");

同样,如果不需要忽略,依然可以像下面这样调用,非常的方便!

StudentBO studentBO = CopyUtils.copy(student, StudentBO::new);

1.3 支持忽略字段值为null的属性

在某些场景下,我们在拷贝的时侯也会忽略字段值为null的属性,这里有了【1.2】的支持,这里实现了获取null属性值的方法,如下:

/**
 * 获取bean中字段值=null的字段
*/
private static String[] getNullPropertyNames(Object source, String... ignoreProperties) {
    final BeanWrapper src = new BeanWrapperImpl(source);
    java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

    Set<String> ignoreList = new HashSet<String>();
    for (java.beans.PropertyDescriptor pd : pds) {
        Object srcValue = src.getPropertyValue(pd.getName());
        if (srcValue == null) {
            ignoreList.add(pd.getName());
        }
    }
    if (ignoreProperties != null) {
        ignoreList.addAll(Arrays.asList(ignoreProperties));
    }
    String[] result = new String[ignoreList.size()];
    return ignoreList.toArray(result);
}

然后,基于此,就可以实现copyPropertiesIgnoreNull忽略null字段

/**
 * 拷贝属性, 支持忽略属性,并自动忽略null字段
 */
public static void copyPropertiesIgnoreNull(Object src, Object target, String... ignoreProperties) {
    BeanUtils.copyProperties(src, target, getNullPropertyNames(src, ignoreProperties));
}

1.4 通用的Bean拷贝

最终,就在内部实现了通用的Bean拷贝~

/**
 * bean拷贝, 支持忽略属性 + 忽略null字段
 */
private static <S, T> T copyInternal(S source, Supplier<T> target, boolean isIgnoreNull, String... ignoreProperties) {
    if (source == null) {
        return null;
    }
    T t = target.get();
    if (isIgnoreNull) {
        copyPropertiesIgnoreNull(source, t, ignoreProperties);
    } else {
        copyProperties(source, t, ignoreProperties);
    }
    return t;
}

根据是否忽略null字段,我这里对外开放了两个重载方法:

1.4.1 拷贝时可指定忽略属性

/**
 * bean拷贝, 支持忽略属性
 */
public static <S, T> T copy(S source, Supplier<T> target, String... ignoreProperties) {
    return copyInternal(source, target, false, ignoreProperties);
}

1.4.2 拷贝时外加忽略null属性

/**
 * bean拷贝, 支持忽略属性, 并自动忽略null字段
 */
public static <S, T> T copyIgnoreNull(S source, Supplier<T> target, String... ignoreProperties) {
    return copyInternal(source, target, true, ignoreProperties);
}

二、拷贝集合对象List

有了拷贝普通对象Bean的基础,拷贝List实际就是循环拷贝!

所以,看到这里,你要不要自己先试一下?

OK,希望咱们想到一块了~

同样封装了一个通用List拷贝,参数与copyInternal同样4个参数,只是将source修改为List<S>类型,返回类型修改为List<T>类型,然后循环~

/**
 * bean list拷贝, 支持忽略属性 + 忽略null字段
 */
private static <S, T> List<T> copyListInternal(List<S> sourceList, Supplier<T> target, boolean isIgnoreNull, String... ignoreProperties) {
    if (sourceList == null) {
        return null;
    }
    List<T> targetList = new ArrayList<>();
    for (S s : sourceList) {
        targetList.add(copyInternal(s, target, isIgnoreNull, ignoreProperties));
    }
    return targetList;
}

同样根据是否忽略null字段,我这里对外开放了两个重载方法:

2.1 拷贝时可指定忽略属性

/**
 * bean list拷贝, 支持忽略属性
 */
public static <S, T> List<T> copyList(List<S> sourceList, Supplier<T> target, String... ignoreProperties) {
    return copyListInternal(sourceList, target, false, ignoreProperties);
}

2.2 拷贝时外加忽略null属性

/**
 * bean list拷贝, 支持忽略属性, 并自动忽略null字段
 */
public static <S, T> List<T> copyListIgnoreNull(List<S> sourceList, Supplier<T> target, String... ignoreProperties) {
    return copyListInternal(sourceList, target, true, ignoreProperties);
}

三、拷贝分页对象Page

Page对象稍微特殊一点,它本身继承了ArrayList

所以,你知道怎么实现吗?

OK,我是这样实现的:

/**
 * bean page拷贝, 支持忽略属性
 */
public static <S, T> Page<T> copyPage(Page<S> sourcePage, Supplier<T> target, String... ignoreProperties) {
    Page<T> targetPage = copy(sourcePage, Page::new);
    for (S s : sourcePage.getResult()) {
        targetPage.add(copy(s, target, ignoreProperties));
    }
    return targetPage;
}

四、整个CopyUtils类源码

虽然在上面已经贴了全部源码,但还是给出完整源码!

package org.tg.book.common.utils;

import com.github.pagehelper.Page;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;

public class CopyUtils extends BeanUtils {

    /**
     * bean page拷贝, 支持忽略属性
     */
    public static <S, T> Page<T> copyPage(Page<S> sourcePage, Supplier<T> target, String... ignoreProperties) {
        Page<T> targetPage = copy(sourcePage, Page::new);
        for (S s : sourcePage.getResult()) {
            targetPage.add(copy(s, target, ignoreProperties));
        }
        return targetPage;
    }

    /**
     * bean list拷贝, 支持忽略属性
     */
    public static <S, T> List<T> copyList(List<S> sourceList, Supplier<T> target, String... ignoreProperties) {
        return copyListInternal(sourceList, target, false, ignoreProperties);
    }

    /**
     * bean list拷贝, 支持忽略属性, 并自动忽略null字段
     */
    public static <S, T> List<T> copyListIgnoreNull(List<S> sourceList, Supplier<T> target, String... ignoreProperties) {
        return copyListInternal(sourceList, target, true, ignoreProperties);
    }

    /**
     * bean拷贝, 支持忽略属性
     */
    public static <S, T> T copy(S source, Supplier<T> target, String... ignoreProperties) {
        return copyInternal(source, target, false, ignoreProperties);
    }

    /**
     * bean拷贝, 支持忽略属性, 并自动忽略null字段
     */
    public static <S, T> T copyIgnoreNull(S source, Supplier<T> target, String... ignoreProperties) {
        return copyInternal(source, target, true, ignoreProperties);
    }

    /**
     * bean list拷贝, 支持忽略属性 + 忽略null字段
     */
    private static <S, T> List<T> copyListInternal(List<S> sourceList, Supplier<T> target, boolean isIgnoreNull, String... ignoreProperties) {
        if (sourceList == null) {
            return null;
        }
        List<T> targetList = new ArrayList<>();
        for (S s : sourceList) {
            targetList.add(copyInternal(s, target, isIgnoreNull, ignoreProperties));
        }
        return targetList;
    }

    /**
     * bean拷贝, 支持忽略属性 + 忽略null字段
     */
    private static <S, T> T copyInternal(S source, Supplier<T> target, boolean isIgnoreNull, String... ignoreProperties) {
        if (source == null) {
            return null;
        }
        T t = target.get();
        if (isIgnoreNull) {
            copyPropertiesIgnoreNull(source, t, ignoreProperties);
        } else {
            copyProperties(source, t, ignoreProperties);
        }
        return t;
    }

    /**
     * 拷贝属性, 支持忽略属性,并自动忽略null字段
     */
    public static void copyPropertiesIgnoreNull(Object src, Object target, String... ignoreProperties) {
        BeanUtils.copyProperties(src, target, getNullPropertyNames(src, ignoreProperties));
    }

    /**
     * 获取bean中字段值=null的字段
     */
    private static String[] getNullPropertyNames(Object source, String... ignoreProperties) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

        Set<String> ignoreList = new HashSet<String>();
        for (java.beans.PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                ignoreList.add(pd.getName());
            }
        }
        if (ignoreProperties != null) {
            ignoreList.addAll(Arrays.asList(ignoreProperties));
        }
        String[] result = new String[ignoreList.size()];
        return ignoreList.toArray(result);
    }
}

五、应用到现有代码

这里主要是搜索BeanUtils.copyProperties,然后将对象代码,换成CopyUtils中的相关实现!

在这里插入图片描述

例如,普通的拷贝就是将上面的3行替换成1行:

return CopyUtils.copy(this, BookBO::new);

再比如还用了拷贝Page,原来是这样写的:

在这里插入图片描述

有了工具类就简化成这样了(实际也是一行代码):

在这里插入图片描述

如果没有下面的业务逻辑,只是拷贝绝对是一行代码,看一个明显的:
在这里插入图片描述

这里我就不一一展示了~


六、测试+Git提交

测试就留给大家了,做好自测有助于保证质量!

另外,养成好习惯,Git一步一提交!

在这里插入图片描述


最后

想要看更多实战好文章,还是给大家推荐我的实战专栏–>《基于SpringBoot+SpringCloud+Vue前后端分离项目实战》,由我和 前端狗哥 合力打造的一款专栏,可以让你从0到1快速拥有企业级规范的项目实战经验!

具体的优势、规划、技术选型都可以在《开篇》试读!

订阅专栏后可以添加我的微信,我会为每一位用户进行针对性指导!

另外,别忘了关注我:天罡gg ,发布新文不容易错过: https://blog.csdn.net/scm_2008

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/874448.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HOT92-最小路径和

leetcode原题链接&#xff1a;最小路径和 题目描述 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 示例 1&#xff1a; 输入&#xff1a;…

基础堆排序(Java 实例代码)

目录 基础堆排序 一、概念及其介绍 二、适用说明 三、过程图示 四、Java 实例代码 src/runoob/heap/Heapify.java 文件代码&#xff1a; 基础堆排序 一、概念及其介绍 堆排序&#xff08;Heapsort&#xff09;是指利用堆这种数据结构所设计的一种排序算法。 堆是一个近…

① vue复习。从安装到使用

vue官网&#xff1a;cn.vuejs.org vue安装 cnpm install -g vue/cli 查看是否安装成功 vue --version 创建一个项目 vue create vue-demo(项目名称) 这个取消掉。空格可选中或者取消。 运行项目&#xff1a; cd 进入到项目下 npm run serve 运行成功后&#xff0c;访问这…

面对算力瓶颈,如何利用CPU解决全链路智能编码?

编者按&#xff1a;英特尔是半导体行业和计算创新领域的全球领先厂商。与合作伙伴一起&#xff0c;英特尔推动了人工智能、5G、智能边缘等转折性技术的创新和应用突破&#xff0c;驱动智能互联世界。不久前&#xff0c;英特尔正式发布了第四代英特尔至强可扩展处理器&#xff0…

计算机网络-物理层(二)- 传输方式

计算机网络-物理层&#xff08;二&#xff09;- 传输方式 串型传输与并行传输 串行传输:是指数据是一个比特一个比特依次发送的&#xff0c;因此在发送端和接收端之间&#xff0c;只需要一条数据传输线路即可 并行传输:是指一次发送n个比特而不是一个比特&#xff0c;因此发送…

前端架构师的能力要求:打造可靠、灵活和可扩展的Web应用

随着互联网技术迅猛发展&#xff0c;现代Web应用程序变得越来越复杂且功能强大。作为一名前端架构师&#xff0c;在这个快节奏且竞争激烈的环境中&#xff0c;你需要具备广泛而深入地技术知识&#xff0c;并且有能力设计、开发和维护高度可靠、灵活和可扩展性强的Web应用。 深入…

popen/pclose 函数

函数作用 如果说system在一定程度上是execl的优化版&#xff0c;那么popen就一定程度上是system的优化版&#xff0c;使用popen不仅可以运行代码&#xff0c;还可以获取运行的输出结果&#xff08;但是system和exec族函数还是非常重要的&#xff0c;也有自己的特定应用场景&am…

从0开始搭建ns3环境以及NetAnim简单使用

一、环境准备 ns3是基于GNU/Linux平台使用C开发的工具软件&#xff0c;在windows系统中安装使用ns3环境&#xff0c;可以使用虚拟机VMware并安装ubuntu系统来实现&#xff0c;现将本教程所用到的虚拟机和系统镜像放到网盘提供下载 名称链接提取码VMware Workstation 17 Proht…

Docker镜像查看下载删除镜像文件的相关命令

1.镜像相关命令 本地查看有哪些镜像文件&#xff1a; docker images镜像的名称就是我们常见的一些软件&#xff0c;镜像相当于把软件和软件所需要的运行环境打包到一个镜像文件里面&#xff0c;将来在通过这个镜像文件创建出对应的容器&#xff0c;容器有了以后这些软件自动的…

system函数

函数作用 执行一个shell的命令 其实通过查看system函数的源码就会发现&#xff0c;system函数调用后会进行一次fork&#xff0c;然后就会在子进程中运行“execl("/bin/sh", "sh", "-c", command, (char *) 0);” 而“sh -c XXX” 这个命令&…

IC设计仿真云架构

对于IC仿真来说&#xff0c;最重要的是要安全、可维护、高性能的的HPC环境环境。 那么云上如何搭建起一套完整的IC仿真云环境呢&#xff1f; 这种架构应该长什么样子&#xff1f; 桌面虚拟化基础架构 将所有桌面虚拟机在数据中心进行托管并统一管理&#xff1b;同时用户能够…

学习笔记整理-JS-04-流程控制语句

文章目录 一、条件语句1. if语句的基本使用2. if else if多条件分支3. if语句算法题4. switch语句5. 三元运算符 二、循环语句1. for循环语句2. for循环算法题3. while循环语句4. break和continue5. do while语句 三、初识算法1. 什么是算法2. 累加器和累乘器3. 穷举法4. 综合算…

python根据已有列计算其他列

根据已有列计算其他列 1、根据已有列新增列2、根据已有列修改其他列 读取数据源 import pandas as pd # 读取智能大师号码信息 path1 r../excelFile-j/flower.csv df_data pd.read_csv(path1) # df_data 内容1、根据已有列新增列 方式一&#xff1a;根据单列 df_data["…

Python遥感开发之分段读取和保存遥感数据

Python遥感开发之分段读取和保存遥感数据 1 分段读取数据2 实现分批读取数据以及进行计算3 实现分批保存成TIF文件&#xff08;所有完整代码&#xff09;4 分段TIF整合到一个TIF5 生成一个空白TIF&#xff08;每个像元值为0的TIF&#xff09; 前言&#xff1a;当遇到批量读取大…

DIP: Spectral Bias of DIP 频谱偏置解释DIP

On Measuring and Controlling the Spectral Bias of the Deep Image Prior 文章目录 On Measuring and Controlling the Spectral Bias of the Deep Image Prior1. 方法原理1.1 动机1.2 相关概念1.3 方法原理频带一致度量与网络退化谱偏移和网络结构的关系Lipschitz-controlle…

DCMM数据管理成熟度之数据治理-数据治理沟通

​01 标准原文 1 概述 数据治理沟通旨在确保组织内全部利益相关者都能及时了解相关政策、标准、流程、角色、职责、计划的最新情况,开展数据管理和应用相关的培训,掌握数据管理相关的知识和技能。数据治理沟通旨在建立与提升跨部门及部门内部数据管理能力,提升数据资产意识,…

读发布!设计与部署稳定的分布式系统(第2版)笔记31_版本问题

1. 在软件与外部环境之间的许多交汇点上&#xff0c;版本控制基本上处于混乱状态 1.1. 不应该为了更新自身系统的API&#xff0c;而让服务消费者被迫与你同时发布新版本 1.2. 多数服务新版本的发布应该具有兼容性 2. 分层的“约定”栈 2.1. 连接握手和持续时间 2.2. 请求组…

华为在ospf area 0单区域的情况下结合pbr对数据包的来回路径进行控制

配置思路&#xff1a; 两边去的包在R1上用mqc进行下一跳重定向 两边回程包在R4上用mqc进行下一跳重定向 最终让内网 192.168.10.0出去的数据包来回全走上面R-1-2-4 192.168.20.0出去的数据包来回全走 下面R1-3-4 R2和R3就是简单ospf配置和宣告&#xff0c;其它没有配置&#…

Python爬虫(十一)_案例:使用正则表达式的爬虫

本章将结合先前所学的爬虫和正则表达式知识&#xff0c;做一个简单的爬虫案例&#xff0c;更多内容请参考:Python学习指南 现在拥有了正则表达式这把神兵利器&#xff0c;我们就可以进行对爬取到的全部网页源代码进行筛选了。 下面我们一起尝试一下爬取内涵段子网站&#xff1…

《C语言深度解剖》.pdf

&#x1f407; &#x1f525;博客主页&#xff1a; 云曦 &#x1f4cb;系列专栏&#xff1a;深入理解C语言 &#x1f4a8;吾生也有涯&#xff0c;而知也无涯 &#x1f49b; 感谢大家&#x1f44d;点赞 &#x1f60b;关注&#x1f4dd;评论 C语言深度解剖.pdf 提取码:yunx