在 Spring 项目里,开发者经常会在 "把数据从一个对象拷贝到另一个对象" 的场景下碰到 BeanUtils
这个工具类。Spring 生态里其实有两套名字相同但实现不同的 BeanUtils
:
- Spring 自带的
org.springframework.beans.BeanUtils
- Apache Commons BeanUtils (
org.apache.commons.beanutils.BeanUtils
)
日常 Spring 项目里最常见的是 Spring 自带的版本,以下也是介绍此版本。
一、Spring 的 BeanUtils 能做什么?
常见方法 | 作用 | 备注 |
---|---|---|
copyProperties(Object source, Object target) |
把源对象的同名同类型属性拷贝到目标对象 | 最常用的方法 |
copyProperties(Object source, Object target, Class<?> editable) |
同上,但只拷贝 editable 类里声明的属性 | 可做白名单 |
copyProperties(Object source, Object target, String... ignoreProperties) |
同上,但忽略指定属性 | 可做黑名单 |
instantiateClass(Class<T> clazz) |
用无参构造器实例化类 | 内部帮你处理了异常 |
findMethod(...) /getPropertyDescriptor(...) |
反射工具 | 框架内部用得多 |
使用时的代码示例:
java
import org.springframework.beans.BeanUtils;
@Data
class Source {
private Long id;
private String name;
private List<String> tags;
}
@Data
class Target {
private Long id;
private String name;
private List<String> tags;
}
public class Demo1 {
public static void main(String[] args) {
Source src = new Source();
src.setId(1L);
src.setName("Tom");
src.setTags(Arrays.asList("java", "spring"));
Target tar = new Target();
// 方法一
BeanUtils.copyProperties(src, tar); // ← 关键一行
System.out.println(tar);
// Target(id=1, name=Tom, tags=[java, spring])
// 方法二
BeanUtils.copyProperties(src, tar, "id", "tags"); // id、tags 不会被拷贝
// 方法三(省略了父类 / 接口声明的白名单)
BeanUtils.copyProperties(src, tar, Base.class); // 只拷贝 Base 中声明的字段
}
}
二、常见陷阱和建议
1、小心浅拷贝
BeanUtils.copyProperties
只复制了字段值,集合 / 数组里的元素仍然是同一引用
java
List<Order> orders = new ArrayList<>();
source.setOrders(orders);
BeanUtils.copyProperties(source, target); // target.orders 与 source.orders 指向同一对象
2、不会触发 setter / getter 里的业务逻辑
BeanUtils.copyProperties
基于反射 + 缓存,性能较快,但也因为直接用反射写字段,任何 setter 里的校验、计算都会被跳过。
3、字段名 / 类型必须完全一致
一个字母的大小写不同或类型不兼容就拷不过去,也不会报错,只是值为 null。
三、什么是反射?
反射(Reflection)是 Java 在运行时动态加载类、探查类结构(字段、方法、构造器),并对其进行实例化、调用或赋值的机制。
你可以把"反射"想象成 Java 语言的"后门":正常情况下,你只能通过 new
或调用公开方法去使用一个类;有了反射,你就可以在运行时"拆开"类、对象、方法、字段,随意查看甚至修改,哪怕它们是 private
的。
java
// 正常写法
Dog dog = new Dog();
dog.setName("旺财");
// 反射写法(等价功能)
Class<?> clazz = Class.forName("com.xxx.Dog"); // 1. 运行时把类加载进来
Object dog = clazz.getDeclaredConstructor().newInstance(); // 2. 创建对象
Method setName = clazz.getMethod("setName", String.class); // 3. 拿方法
setName.invoke(dog, "旺财"); // 4. 调用方法
总结,反射就是运行时"透视"并操控 Java 类的超能力,框架靠它实现"零配置"和"动态扩展",代价是性能略慢、破坏封装,需要谨慎使用。
四、考虑包含 null 的情形
如果 srouce 或 target 本身为 null,Spring 的 BeanUtils.copyProperties
会立即抛出 IllegalArgumentException
,拷贝动作根本不会发生。
java
// 源码
public static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
...
}
如果源对象的字段本身就是 null,那么目标字段一定会被覆写成 null(或基本类型的默认值),不会保留原值,也不会报错。