源码
查看代码
java
// 定义一个静态方法copyProperties,用于从一个对象复制属性到另一个对象。
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException {
// 检查源对象是否为空,如果为空则抛出异常。
Assert.notNull(source, "Source must not be null");
// 检查目标对象是否为空,如果为空则抛出异常。
Assert.notNull(target, "Target must not be null");
// 获取目标对象的类信息。
Class<?> actualEditable = target.getClass();
// 如果传入了editable参数,则检查目标对象是否是editable的实例。
if (editable != null) {
if (!editable.isInstance(target)) {
// 如果目标对象不是editable的实例,则抛出非法参数异常。
String var10002 = target.getClass().getName();
throw new IllegalArgumentException("Target class [" + var10002 + "] not assignable to editable class [" + editable.getName() + "]");
}
// 将actualEditable设置为editable。
actualEditable = editable;
}
// 获取actualEditable类的属性描述符数组。
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
// 如果传入了ignoreProperties参数,则将其转换为不可变集合。
Set<String> ignoredProps = ignoreProperties != null ? new HashSet(Arrays.asList(ignoreProperties)) : null;
// 如果actualEditable和source的类不相同,则为source的类创建CachedIntrospectionResults。
CachedIntrospectionResults sourceResults = actualEditable != source.getClass() ? CachedIntrospectionResults.forClass(source.getClass()) : null;
// 遍历目标对象的属性描述符数组。
for(int var10 = 0; var10 < var9; ++var10) {
PropertyDescriptor targetPd = var8[var10];
// 获取目标属性的写方法。
Method writeMethod = targetPd.getWriteMethod();
// 如果写方法不为空且目标属性没有被忽略,则继续处理。
if (writeMethod != null && (ignoredProps == null || !ignoredProps.contains(targetPd.getName()))) {
// 如果sourceResults不为空,则从sourceResults获取属性描述符,否则使用目标对象的属性描述符。
PropertyDescriptor sourcePd = sourceResults != null ? sourceResults.getPropertyDescriptor(targetPd.getName()) : targetPd;
// 如果sourcePd不为空,则继续处理。
if (sourcePd != null) {
// 获取源属性的读方法。
Method readMethod = sourcePd.getReadMethod();
// 如果读方法不为空且写方法与读方法兼容,则继续处理。
if (readMethod != null && isAssignable(writeMethod, readMethod, sourcePd, targetPd)) {
try {
// 设置读方法为可访问。
ReflectionUtils.makeAccessible(readMethod);
// 执行读方法,获取源属性的值。
Object value = readMethod.invoke(source);
// 设置写方法为可访问。
ReflectionUtils.makeAccessible(writeMethod);
// 执行写方法,将值设置到目标对象。
writeMethod.invoke(target, value);
} catch (Throwable var16) {
// 如果复制过程中发生异常,则抛出FatalBeanException。
throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var16);
}
}
}
}
}
}
执行流程
注意事项
类型不匹配
查看代码
java
class Source {
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
class Target {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public class TestBeanUtils {
public static void main(String[] args) {
Source source = new Source();
source.setId(123L);
Target target = new Target();
BeanUtils.copyProperties(source, target);
System.out.println(target.getId()); //拷贝失败,输出null
}
}
在这个例子中,由于 Source
的 id
是 Long
类型,而 Target
的 id
是 String
类型,尝试复制属性失败,输出null。
属性名称不一致
查看代码
java
class Source {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
class Target {
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
public class TestBeanUtils {
public static void main(String[] args) {
Source source = new Source();
source.setUsername("cengxuyuan");
Target target = new Target();
BeanUtils.copyProperties(source, target);
System.out.println(target.getUserName()); // 输出 null,因为属性名称不一致
}
}
在这个例子中,由于 Source
的 username
属性和 Target
的 userName
属性名称不一致,属性不会被复制。
只进行浅拷贝
查看代码
java
class Address {
private String street;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
class Person {
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
class PersonCopy {
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
public class TestBeanUtils {
public static void main(String[] args) {
Address address1 = new Address();
address1.setStreet("cengxuyuan Main St");
Person person = new Person();
person.setAddress(address1);
Address address2 = new Address();
address2.setStreet("cengxuyuan Main St");
PersonCopy personCopy = new PersonCopy();
personCopy.setAddress(address2);
BeanUtils.copyProperties(person, personCopy);
// 修改源对象的地址
person.getAddress().setStreet("cengxuyuan center");
System.out.println(personCopy.getAddress().getStreet()); // 输出 "cengxuyuan center",因为浅拷贝导致引用相同
}
}
在这个例子中,复制后的Person
和 PersonCopy
的 Address
属性是同一个对象的引用,所以修改源对象的 Address
也会影响到复制后的对象。
Null 值覆盖
查看代码
java
class Source {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Target {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class TestBeanUtils {
public static void main(String[] args) {
Source source = new Source();
Target target = new Target();
target.setName("cengxuyuan");
BeanUtils.copyProperties(source, target);
System.out.println(target.getName()); // 输出 null,因为源对象的 name 是 null
}
}
在这个例子中,由于 Source
的 name
属性是 null
,复制后 Target
的 name
属性也被设置为 null
,覆盖了原有的值。
读写方法访问权限
写方法访问权限:目标对象的属性必须有相应的写方法(setter),且这些方法必须是 public 的。如果写方法不是 public 的,BeanUtils.copyProperties
将无法复制属性。
读方法访问权限:源对象的属性必须有相应的读方法(getter),且这些方法必须是 public 的。如果读方法不是 public 的,BeanUtils.copyProperties
将无法读取属性值。
查看代码
java
class Target {
private String name;
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
}
public class TestBeanUtils {
public static void main(String[] args) {
Source source = new Source();
source.setName("cengxuyuan");
Target target = new Target();
BeanUtils.copyProperties(source, target);
System.out.println(target.getName()); // 输出 null,因为Target对象的 setName 是 private 的
}
}
is布尔变量
JavaBeans 规范中属性的命名和访问方法有以下规则:
- 基本数据类型的属性:getter 方法为
getXxx
,setter 方法为setXxx
。 - 基本布尔类型的属性:getter 方法为
isXxx
,setter 方法为setXxx
。 - 包装类型的属性:getter 方法为
getXxx
,setter 方法为setXxx
。
查看代码
java
@Data
class Source {
private boolean isCengxuyuan;
}
@Data
class Target {
private Boolean isCengxuyuan;
}
public class TestBeanUtils {
public static void main(String[] args) {
Source source = new Source();
source.setCengxuyuan(true);
Target target = new Target();
BeanUtils.copyProperties(source, target);
System.out.println(target.getIsCengxuyuan()); // 输出为 null
}
}