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

概述
Spring BeanUtils 是 Spring 框架中的一部分,它提供了一套用于简化 Java 对象属性操作的工具类。尽管它的名字暗示了它可能与 Java Bean 相关,但实际上它并不操作 Java Bean 本身,而是操作对象的属性。
BeanUtils 的核心功能是提供属性复制的方法,这在需要将一个对象的属性值复制到另一个对象时非常有用。
Spring BeanUtils 的主要功能如下:
- 属性复制:
copyProperties
方法可以将一个对象的属性值复制到另一个对象中,前提是这两个对象中必须存在相同名称和类型的属性。 - 忽略特定属性:
copyProperties
方法可以指定一个或多个属性不被复制,通过传递一个字符串数组或单个字符串参数来实现。 - 类型匹配:Spring BeanUtils 会在复制属性时检查源对象和目标对象的属性类型是否匹配,如果不匹配,则不会复制该属性。
- 编辑域限制:可以指定哪些类及其父类中的属性可以被复制,通过传递一个
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; |
| |
| |
| |
| |
| |
| |
| |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Source { |
| |
| |
| private String id; |
| private String username; |
| } |
| package com.artisan.bootbeanutils.entity; |
| |
| import lombok.AllArgsConstructor; |
| import lombok.Data; |
| import lombok.NoArgsConstructor; |
| |
| |
| |
| |
| |
| |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Target { |
| |
| |
| private 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 { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| @Test |
| public void testDiffPropertyType() { |
| |
| Source source = new Source("1", "artisan"); |
| Target target = new Target(); |
| |
| |
| 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; |
| } |
单元测试
| |
| |
| |
| |
| |
| |
| |
| |
| @Test |
| public void testWrappedValue() { |
| |
| |
| |
| SourceWrappedValue wrappedValue = new SourceWrappedValue(1L, "artisan"); |
| TargetPrimitiveValue primitiveValue = new TargetPrimitiveValue(); |
| |
| BeanUtils.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(); |
| |
| 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; |
| |
| |
| private boolean isDone; |
| |
| |
| private boolean finished; |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class TargetBoolean { |
| private Long id; |
| private String username; |
| |
| |
| private Boolean isDone; |
| |
| |
| private Boolean finished; |
| } |
单元测试
| |
| |
| |
| @Test |
| public void testBooleanAndIsXxx() { |
| |
| SourceBoolean sourceBoolean = new SourceBoolean(1L, "artisan", true, false); |
| TargetBoolean targetBoolean = new TargetBoolean(); |
| |
| BeanUtils.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; |
| } |
单元测试
| |
| |
| |
| @SpringBootTest |
| class BootBeanUtilsApplicationTests2 { |
| |
| |
| |
| |
| |
| |
| |
| @Test |
| public void testNullCopyToNotNull() { |
| |
| |
| Source source = new Source(); |
| source.setId("1"); |
| System.out.println("original source data: " + source); |
| |
| |
| Target target = new Target(); |
| target.setUsername("artisan"); |
| System.out.println("original target data: " + target); |
| |
| |
| BeanUtils.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 |
| @AllArgsConstructor |
| public static class InnerClass { |
| public String innerName; |
| } |
| } |
| @Data |
| @AllArgsConstructor |
| @NoArgsConstructor |
| public class Target { |
| private Long id; |
| private String username; |
| |
| private InnerClass innerClass; |
| |
| @Data |
| @AllArgsConstructor |
| public static class InnerClass { |
| public String innerName; |
| } |
| } |
单元测试
| |
| |
| |
| @SpringBootTest |
| class BootBeanUtilsApplicationTests4 { |
| |
| |
| |
| |
| |
| @Test |
| public void testInnerClassCopy() { |
| |
| Source source = new Source(1L, "artisan", new Source.InnerClass("artisan-inner")); |
| Target target = new Target(); |
| |
| |
| BeanUtils.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; |
| |
| } |
单元测试
| |
| |
| |
| |
| |
| @SpringBootTest |
| class BootBeanUtilsApplicationTests5 { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| @Test |
| public 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中的值是否有变化,用于验证是否是浅复制...."); |
| |
| 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 { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| @Test |
| public void testNullCopyToNotNull() throws InvocationTargetException, IllegalAccessException { |
| |
| |
| Source source = new Source(); |
| source.setId("1"); |
| |
| |
| Target target = new Target(); |
| target.setUsername("artisan"); |
| |
| |
| BeanUtils.copyProperties(source, target); |
| |
| System.out.println("copied data: " + target); |
| |
| |
| System.out.println("============使用Apache Common 的BeanUtils============"); |
| |
| |
| org.apache.commons.beanutils.BeanUtils.copyProperties(target, source); |
| System.out.println("copied data: " + target); |
| Assert.notNull(target.getUsername(), "username不应为空, 请检查"); |
| |
| } |
| } |

Performance - BeanUtils vs 原生set
| |
| |
| |
| @SpringBootTest |
| class BootBeanUtilsApplicationTests6 { |
| |
| |
| @Test |
| public 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 的基本使用