Java Review - Spring BeanUtils 踩坑记

文章目录

  • 概述
  • Spring BeanUtils基本使用
  • Code
    • 忽略了属性类型导致拷贝失败
      • 同一字段在不同的类中定义的类型不一致
      • 同一个字段分别使用包装类和基本类型且没有传递实际值
      • 布尔类型的属性分别使用了基本类型和包装类型且属性名使用is开头
    • null值覆盖导致数据异常
    • 内部类数据无法成功拷贝
    • 浅拷贝 vs 深拷贝
    • 引入了错误的包
    • Performance - BeanUtils vs 原生set
  • Apache Commons BeanUtils

在这里插入图片描述


概述

Spring BeanUtils 是 Spring 框架中的一部分,它提供了一套用于简化 Java 对象属性操作的工具类。尽管它的名字暗示了它可能与 Java Bean 相关,但实际上它并不操作 Java Bean 本身,而是操作对象的属性。

BeanUtils 的核心功能是提供属性复制的方法,这在需要将一个对象的属性值复制到另一个对象时非常有用。

Spring BeanUtils 的主要功能如下:

  1. 属性复制copyProperties 方法可以将一个对象的属性值复制到另一个对象中,前提是这两个对象中必须存在相同名称和类型的属性。
  2. 忽略特定属性copyProperties 方法可以指定一个或多个属性不被复制,通过传递一个字符串数组或单个字符串参数来实现。
  3. 类型匹配:Spring BeanUtils 会在复制属性时检查源对象和目标对象的属性类型是否匹配,如果不匹配,则不会复制该属性。
  4. 编辑域限制:可以指定哪些类及其父类中的属性可以被复制,通过传递一个 Class<?> 参数来实现。

使用 Spring BeanUtils 的好处是能够减少样板代码,提高代码的可读性和可维护性。例如,当你需要创建一个新对象并将其设置为与另一个对象相同的状态时,使用 BeanUtils 可以避免手动设置每个属性。

Spring BeanUtils 的使用场景非常广泛,尤其在需要对象间属性同步或数据传输对象(Data Transfer Object, DTO)转换时,它提供了一个简单而有效的解决方案。在 Spring MVC 中,它也常用于将请求参数映射到服务层的对象中。

需要注意的是,Spring BeanUtils 和 Apache Commons BeanUtils 是两个不同的库,虽然它们都提供了类似的功能,但在使用时需要明确区分。Spring 的 BeanUtils 通常被认为在性能上进行了优化,并且与 Spring 框架的其他部分集成得更好。


Spring BeanUtils基本使用

基本使用很简单,这里就不演示了,主要是熟悉下API即可 。
在这里插入图片描述

可以看下面的链接。

Spring - Copying properties using BeanUtils


Code

在这里插入图片描述

请注意看注释


忽略了属性类型导致拷贝失败

同一字段在不同的类中定义的类型不一致

两个Entity

同样为id , 一个是String类型,一个是Long类型 , 此时如果使用BeanUtils.copyProperties进行拷贝,会出现拷贝失败的现象,导致对应的字段为null

package com.artisan.bootbeanutils.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Data
@AllArgsConstructor
@NoArgsConstructor
public class Source {// id 类型为 Stringprivate String id;private String username;
}
package com.artisan.bootbeanutils.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Target {// id 类型为 Longprivate Long id;private String username;
}

单元测试

package com.artisan.bootbeanutils;import com.artisan.bootbeanutils.entity.Source;
import com.artisan.bootbeanutils.entity.Target;
import com.artisan.bootbeanutils.entity2.SourceWrappedValue;
import com.artisan.bootbeanutils.entity2.TargetPrimitiveValue;
import com.artisan.bootbeanutils.entity3.SourceBoolean;
import com.artisan.bootbeanutils.entity3.TargetBoolean;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;/*** 属性类型不一致导致拷贝失败*/
@SpringBootTest
class BootBeanUtilsApplicationTests1 {/*** 同一属性的类型不同* <p>* 在开发中,很可能会出现同一字段在不同的类中定义的类型不一致* 例如ID,可能在A类中定义的类型为Long,在B类中定义的类型为String,* 此时如果使用BeanUtils.copyProperties进行拷贝,会出现拷贝失败的现象,导致对应的字段为null*/@Testpublic void testDiffPropertyType() {// Source 和 Target  虽然都有 id属性,但类型却不同 一个为String  一个为LongSource source = new Source("1", "artisan");Target target = new Target();// 通过BeanUtils的copyProperties方法完成对象之间属性的拷贝BeanUtils.copyProperties(source, target);System.out.println(source);System.out.println(target);// 输出Assert.notNull(target.getUsername(), "copy过来的username属性不应为null, 请检查");Assert.notNull(target.getId(), "copy过来的id属性不应为null, 请检查");}
}

在这里插入图片描述


同一个字段分别使用包装类和基本类型且没有传递实际值

两个Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SourceWrappedValue {// 包装类型private Long id;private String username;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TargetPrimitiveValue {// 基本类型private long id;private String username;
}

单元测试

 /*** 如果同一个字段分别使用包装类和基本类型,在没有传递实际值的时候,会出现异常* <p>* 在没有传递实际值的时候,会出现异常* 在没有传递实际值的时候,会出现异常* 在没有传递实际值的时候,会出现异常* </p>*/@Testpublic void testWrappedValue() {// 在传递了实际的值的情况下, 不会抛出异常// 在传递了实际的值的情况下, 不会抛出异常// 在传递了实际的值的情况下, 不会抛出异常SourceWrappedValue wrappedValue = new SourceWrappedValue(1L, "artisan");TargetPrimitiveValue primitiveValue = new TargetPrimitiveValue();// 属性copyBeanUtils.copyProperties(wrappedValue, primitiveValue);System.out.println(primitiveValue);System.out.println(wrappedValue);// 输出Assert.notNull(primitiveValue.getId(), "copy过来的id属性不应为null, 请检查");Assert.notNull(primitiveValue.getUsername(), "copy过来的username属性不应为null, 请检查");System.out.println("========================");// 在没有传递了实际的值的情况下, 会抛出异常// 在没有传递了实际的值的情况下, 会抛出异常// 在没有传递了实际的值的情况下, 会抛出异常SourceWrappedValue sourceWrappedValue = new SourceWrappedValue();sourceWrappedValue.setUsername("artisanTest");TargetPrimitiveValue targetPrimitiveValue = new TargetPrimitiveValue();// 属性copy (这里就会抛出异常 FatalBeanException: Could not copy property 'id' from source to target)BeanUtils.copyProperties(sourceWrappedValue, targetPrimitiveValue);System.out.println(sourceWrappedValue);System.out.println(targetPrimitiveValue);Assert.notNull(targetPrimitiveValue.getId(), "copy过来的id属性不应为null, 请检查");Assert.notNull(targetPrimitiveValue.getUsername(), "copy过来的username属性不应为null, 请检查");}

在这里插入图片描述


布尔类型的属性分别使用了基本类型和包装类型且属性名使用is开头

两个Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SourceBoolean {private Long id;private String username;// 基本类型,且属性名如果使用is开头private boolean isDone;// 基本类型,属性名没有使用is开头private boolean finished;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TargetBoolean {private Long id;private String username;// 包装类型,且属性名如果使用is开头private Boolean isDone;// 基本类型,属性名没有使用is开头private Boolean finished;
}

单元测试

    /*** 如果一个布尔类型的属性分别使用了基本类型和包装类型,且属性名如果使用is开头,例如isDone,也会导致拷贝失败*/@Testpublic void testBooleanAndIsXxx() {// 在传递了实际的值的情况下, 不会抛出异常SourceBoolean sourceBoolean = new SourceBoolean(1L, "artisan", true, false);TargetBoolean targetBoolean = new TargetBoolean();// 属性copyBeanUtils.copyProperties(sourceBoolean, targetBoolean);System.out.println(sourceBoolean);System.out.println(targetBoolean);// 输出Assert.notNull(targetBoolean.getIsDone(), "copy过来的isDone属性不应为null, 请检查");Assert.notNull(targetBoolean.getFinished(), "copy过来的finished属性不应为null, 请检查");}

在这里插入图片描述


null值覆盖导致数据异常

两个Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Source {private String id;private String username;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Target {private String id;private String username;
}

单元测试


/*** null值覆盖导致数据异常*/
@SpringBootTest
class BootBeanUtilsApplicationTests2 {/*** 开发过程中,可能会有部分字段拷贝的需求,被拷贝的数据里面如果某些字段有null值存在,* 但是对应的需要被拷贝过去的数据的相同字段的值并不为null,* 如果直接使用 BeanUtils.copyProperties 进行数据拷贝,就会出现被拷贝数据的null值覆盖拷贝目标数据的字段,导致原有的数据失效*/@Testpublic void testNullCopyToNotNull() {// 模拟 username为nullSource source = new Source();source.setId("1");System.out.println("original source data: " + source);// 模拟 username不为nullTarget target = new Target();target.setUsername("artisan");System.out.println("original target  data: " + target);// 属性copyBeanUtils.copyProperties(source, target);System.out.println("copied target data: " + target);Assert.notNull(target.getUsername(), "username不应为空, 请检查");}
}

在这里插入图片描述


内部类数据无法成功拷贝

Entity


@Data
@AllArgsConstructor
@NoArgsConstructor
public class Source {private Long id;private String username;// 内部类private InnerClass innerClass;@Data@AllArgsConstructorpublic static class InnerClass {public String innerName;}
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Target {private Long id;private String username;// 内部类private InnerClass innerClass;@Data@AllArgsConstructorpublic static class InnerClass {public String innerName;}
}

单元测试


/*** 内部类数据无法成功拷贝*/
@SpringBootTest
class BootBeanUtilsApplicationTests4 {/*** 内部类数据无法正常拷贝,即便类型和字段名均相同也无法拷贝成功*/@Testpublic void testInnerClassCopy() {//  模拟内部类Source source = new Source(1L, "artisan", new Source.InnerClass("artisan-inner"));Target target = new Target();// 属性copyBeanUtils.copyProperties(source, target);System.out.println("source data: " + source);System.out.println("copied data: " + target);Assert.notNull(target.getInnerClass().getInnerName(), "Target#InnerClass#innername不应为空, 请检查");}
}

在这里插入图片描述


浅拷贝 vs 深拷贝

Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PojoA {private String name;private PojoB pojoB;}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PojoB {private String info;}

单元测试


/*** BeanUtils.copyProperties是浅拷贝* <p>* 一旦在拷贝后修改了原始对象的引用类型的数据,就会导致拷贝数据的值发生异常,这种问题排查起来比较困难*/
@SpringBootTest
class BootBeanUtilsApplicationTests5 {/*** 浅拷贝是指创建一个新对象,该对象的属性值与原始对象相同,但对于引用类型的属性,仍然共享相同的引用。* 也就是说在浅拷贝下,当原始内容的引用属性值发生变化时,被拷贝对象的引用属性值也会随之发生变化。* <p>* 深拷贝是指创建一个新对象,该对象的属性值与原始对象相同,包括引用类型的属性。* 深拷贝会递归复制引用对象,创建全新的对象,所以深拷贝拷贝后的对象与原始对象完全独立。*/@Testpublic void testShadowCopy() {PojoA sourcePojoA = new PojoA("artisan", new PojoB("pojoB"));PojoA targetPojoA = new PojoA();// 属性复制BeanUtils.copyProperties(sourcePojoA, targetPojoA);System.out.println(targetPojoA);System.out.println("修改源sourcePojoA中对象的属性值,观察targetPojoA中的值是否有变化,用于验证是否是浅复制....");// 修改source的属性,观察target属性值的变化sourcePojoA.getPojoB().setInfo("测试Modify");System.out.println(targetPojoA);// 浅拷贝  原始对象值被修改后,目标对象的值也会被修改Assert.isTrue("测试Modify".equals(targetPojoA.getPojoB().getInfo()), "浅复制BeanUtils.copyProperties");}
}

在这里插入图片描述


引入了错误的包

  <dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>1.9.4</version></dependency>
package com.artisan.bootbeanutils;import com.artisan.bootbeanutils.entity5.Source;
import com.artisan.bootbeanutils.entity5.Target;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;import java.lang.reflect.InvocationTargetException;/*** 导包错误导致拷贝数据异常*/
@SpringBootTest
class BootBeanUtilsApplicationTests3 {/*** 如果项目中同时引入了Spring的beans包和Apache的beanutils包,* 在导包的时候,如果导入错误,很可能导致数据拷贝失败,排查起来也不太好发现。* <p>* 我们通常使用的是Spring包中的拷贝方法* <p>* //org.springframework.beans.BeanUtils(源对象在左边,目标对象在右边)* public static void copyProperties(Object source, Object target) throws BeansException* <p>* //org.apache.commons.beanutils.BeanUtils(源对象在右边,目标对象在左边)* public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException*/@Testpublic void testNullCopyToNotNull() throws InvocationTargetException, IllegalAccessException {// 模拟 username为nullSource source = new Source();source.setId("1");// 模拟 username不为nullTarget target = new Target();target.setUsername("artisan");// 属性copyBeanUtils.copyProperties(source, target);System.out.println("copied data: " + target);//Assert.notNull(target.getUsername(), "username不应为空, 请检查");System.out.println("============使用Apache Common 的BeanUtils============");// Apache的BeanUtils属性copy  --> 源对象在右边,目标对象在左边org.apache.commons.beanutils.BeanUtils.copyProperties(target, source);System.out.println("copied data: " + target);Assert.notNull(target.getUsername(), "username不应为空, 请检查");}
}

在这里插入图片描述


Performance - BeanUtils vs 原生set


/*** BeanUtils.copyProperties底层是通过反射获取到对象的set和get方法,再通过get、set完成数据的拷贝,整体拷贝效率较低*/
@SpringBootTest
class BootBeanUtilsApplicationTests6 {@Testpublic void testPerformance() {PojoA sourcePojoA = new PojoA("artisan", new PojoB("pojoB"));PojoA targetPojoA = new PojoA();StopWatch stopWatch = new StopWatch("BeanUtils#copyProperties Vs Set");stopWatch.start("copyProperties");for (int i = 0; i < 50000; i++) {BeanUtils.copyProperties(sourcePojoA, targetPojoA);}stopWatch.stop();stopWatch.start("set");for (int i = 0; i < 50000; i++) {targetPojoA.setPojoB(sourcePojoA.getPojoB());targetPojoA.setName(sourcePojoA.getName());}stopWatch.stop();// 打印时间System.out.println(stopWatch.prettyPrint());}
}

在这里插入图片描述


Apache Commons BeanUtils

Apache Commons BeanUtils 的基本使用
在这里插入图片描述

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

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

相关文章

图解算法数据结构-LeetBook-回溯01_机械累加器

请设计一个机械累加器&#xff0c;计算从 1、2… 一直累加到目标数值 target 的总和。注意这是一个只能进行加法操作的程序&#xff0c;不具备乘除、if-else、switch-case、for 循环、while 循环&#xff0c;及条件判断语句等高级功能。 注意&#xff1a;不能用等差数列求和公式…

RK3399平台入门到精通系列讲解(实验篇)IO 多路复用实验之poll实验

🚀返回总目录 文章目录 一、IO 多路复用:poll介绍二、实验源码2.1、Makefile2.2、poll 实验驱动2.3、poll 驱动测试应用程序一、IO 多路复用:poll介绍 IO 多路复用是一种同步的 IO 模型。IO 多路复用可以实现一个进程监视多个文件描述符。 一旦某个文件描述符准备就绪,就通…

osg - 光照

OSG全面支持 OpenGL 的光照特性&#xff0c;包括材质属性(material property)、光照属性(light property)和光照模型 (lighting model)。与 OpenGL 相似&#xff0c;OSG中的光源也是不可见的&#xff0c;而非渲染一个灯泡或其他自然形状。同样&#xff0c;光源会创建着色效果&a…

进程的程序替换(exec函数)【Linux】

进程的程序替换详解exec函数【Linux】 程序替换的原理exec系列函数函数理解命令理解&#xff08;助记&#xff09; 关于程序替换中环境变量的解释exec函数之间的关系exec函数的使用execlexeclpexecleexecv 程序替换的原理 进程的程序替换就是让子进程执行新程序&#xff0c; 执…

QT_02 窗口属性、信号槽机制

QT - 窗口属性、信号槽机制 1. 设置窗口属性 窗口设置 1,标题 2,大小 3,固定大小 4,设置图标在 widget.cpp 文件中&#xff1a; //设置窗口大小,此时窗口是可以拉大拉小的 //1参:宽度 //2参:高度 this->resize(800, 600); //设置窗口标题 this->setWindowTitle("…

Docker中的核心概念

1.镜像 Image 一个镜像就代表一个软件。mysql镜像、redis镜像、mq镜像 2.容器 Container 一个镜像运行一次就会生成一个容器&#xff0c;容器就是一个运行的软件服务。 3.远程仓库 Repository 远程仓库用来存储所有软件的镜像&#xff0c;Docker Hub 4.本地仓库 用来存储…

Ubuntu18.04配置静态ip

文章目录 查看网卡名、ip地址、网关切换root用户&#xff0c;进入配置文件配置静态IP 查看网卡名、ip地址、网关 首先查看网卡名、ip地址、网关&#xff0c;找到对应的网卡名并记录其地址 ifconfigroute -n切换root用户&#xff0c;进入配置文件 sudo -icd /etc/netplanvim …

听GPT 讲Rust源代码--compiler(11)

File: rust/compiler/rustc_mir_transform/src/simplify.rs 在Rust源代码中&#xff0c;rust/compiler/rustc_mir_transform/src/simplify.rs文件是Rust编译器中一系列进行MIR&#xff08;中间表示&#xff09;简化的转换的实现。MIR是Rust编译器中用于进行优化和代码生成的中间…

深入理解 BEM:前端开发中的命名约定革命

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Python+OpenCV 零基础学习笔记(6):ROI

文章目录 相关链接运行环境前言ROI颜色区域分割颜色通道合并 相关链接 【2022B站最好的OpenCV课程推荐】OpenCV从入门到实战 全套课程 CSDN标题里个括号对应视频的分P OpenCVPython CSDN专栏 Gitee 项目地址 运行环境 Python:3.11.5Anaconda:23.7.4IDE:vscode运行环境&#x…

WebSocket的优点和缺点:一文详解。

WebSocket 的优缺点 WebSocket 协议是一种双向通信协议&#xff0c;它使用单个 TCP 连接实现全双工通信&#xff0c;这使它比传统的 HTTP 协议更有效率。 WebSocket 优点 双向通信&#xff1a; WebSocket 协议支持双向通信&#xff0c;使服务器和客户端之间的通信更加方便和快…

Elasticsearch 中映射参数doc_values 和 fielddata分析比较

一、doc_values 默认情况下&#xff0c;大部分字段是索引的&#xff0c;这样让这些字段可被搜索。倒排索引&#xff08;inverted index&#xff09;允许查询请求在词项列表中查找搜索项&#xff08;search term&#xff09;&#xff0c;并立即获得包含该词项的文档列表。 倒排…

vscode 格式化代码后反而出现红色波浪线格式报错

表现&#xff1a; vscode 代码文件格式化之后&#xff0c;反而出现红色波浪线&#xff0c;提示 应该换行/缩进不正确 等等格式不规范之类的信息。 原因&#xff1a; 因为同时开启了两个格式化插件&#xff0c;且两者的规则有冲突。 就我自己的情况而言&#xff1a;格式化代…

漫谈广告机制设计 | 【预告】万剑归宗:机制设计提高平台广告收入的绝招

读者们好&#xff0c;新年快乐&#xff0c;祝大家新的一年工作顺利&#xff0c;万事如意。 假期更新了两篇文章oCPC实践录 | 以基木鱼为例谈线索类有效转化的设计与智能客服的问题和oCPC实践录 | 目标ROI的出价与转化回传调控算法&#xff0c;欢迎讨论、评论、点赞、分享、转发…

2023年为何YOLO成为最热门视觉检测技术?猫头虎带您揭秘其背后的原因!

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通Golang》…

查询运行的java程序线程总数

&#xff08;1&#xff09;方法一 ps -ef|grep java pstree -p pid|wc -l 安装pstree包&#xff0c;yum install psmisc &#xff08;2&#xff09;方法二 top -H -p pid

互联网分布式应用之RabbitMQ

RabbitMQ Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。 课程内容的介绍 1. RabbitMQ介绍安装 2. Rabbi…

人工智能在金融领域的应用存在的4大挑战

金融服务供应商应该有计划地应对AI面临的难题 金融行业投资人工智能热潮带来有关数据安全和透明度的新问题。由于数据管理实践随着新的 AI 解决方案的引入而不断发展&#xff0c;应对这些新问题以及金融服务领域 AI 面临的其他挑战尤为重要。各组织必须认识到可能面临以下挑战…

2024腾讯云轻量应用服务器详细介绍_轻量全解析

腾讯云轻量应用服务器开箱即用、运维简单的轻量级云服务器&#xff0c;CPU内存带宽配置高并且价格特别便宜&#xff0c;大带宽&#xff0c;但是限制月流量。轻量2核2G3M带宽62元一年、2核2G4M优惠价118元一年&#xff0c;540元三年、2核4G5M带宽218元一年&#xff0c;756元3年、…

指令周期流程图相关题目

已知CPU结构如下图所示&#xff0c;其中包括一个累加器AC、一个状态寄存器和其他几个寄存器。各部分之间的连线表示数据通路&#xff0c;箭头表示信息传递方向。试完成以下工作&#xff1a;①写出图中四个寄存器A、B、C、D的名称和作用&#xff1b;②简述完成指令ADD Y的数据通…