前语
咱们好,我是田螺。
咱们日常开发中,常常涉及到DO、DTO、VO
方针特点仿制赋值,很容易想到org.springframework.beans.BeanUtils
的copyProperties
。它会主动通过反射机制获取源方针和方针方针的特点,并将对应的特点值进行仿制。能够削减手动编写特点仿制代码的工作量,进步代码的可读性和维护性。
可是你知道嘛?运用BeanUtils
的copyProperties
,会有好几个坑呢,今日田螺哥个咱们盘点一下哈:
- 大众号:捡田螺的小男孩 (有田螺精心原创的面试PDF)
- github地址,感谢每颗star:github
第1个坑: 类型不匹配
@Data
public class SourceBean {
private Long age;
}
@Data
public class TargetBean {
private String age;
}
public class Test {
public static void main(String[] args) {
SourceBean source = new SourceBean();
source.setAge(25L);
TargetBean target = new TargetBean();
BeanUtils.copyProperties(source, target);
System.out.println(target.getAge()); //仿制赋值失利,输出null
}
}
在上述demo
中,源方针SourceBean
的age
特点是一个Long
类型,而方针方针TargetBean
的age
特点是一个String
类型。因为类型不匹配,BeanUtils.copyProperties
不会赋值成功的。我跑demo
的成果,操控台输出null
。
第2个坑: BeanUtils.copyProperties是浅仿制
先给咱们温习一下,什么是深仿制?什么是浅仿制?
- 浅仿制是指创建一个新方针,该方针的特点值与原始方针相同,但关于引证类型的特点,仍然共享相同的引证。换句话说,浅仿制只仿制方针及其引证,而不仿制引证指向的方针本身。
- 深仿制是指创建一个新方针,该方针的特点值与原始方针相同,包括引证类型的特点。深仿制会递归仿制引证方针,创建全新的方针,以确保仿制后的方针与原始方针完全独立。
我再给个代码demo
给咱们看看哈:
public class Address {
private String city;
//getter 和 setter 办法省掉
}
public class Person {
private String name;
private Address address;
//getter 和 setter 办法省掉
}
Person sourcePerson = new Person();
sourcePerson.setName("John");
Address address = new Address();
address.setCity("New York");
sourcePerson.setAddress(address);
Person targetPerson = new Person();
BeanUtils.copyProperties(sourcePerson, targetPerson);
sourcePerson.getAddress().setCity("London");
System.out.println(targetPerson.getAddress().getCity()); // 输出为 "London"
在上述示例中,源方针Person
的特点address
是一个引证类型。当运用BeanUtils.copyProperties
办法进行特点仿制时,实际上只仿制了引证,即方针方针targetPerson
的 address
特点引证和源方针 sourcePerson
的 address
特点引证指向同一个方针。因而,当修正源方针的address
方针时,方针方针的address
方针也会被修正。
咱们日常开发中,要留意这个坑哈~
第3个坑:特点称号不一致
public class SourceBean {
private String username;
// getter 和 setter 办法省掉
}
public class TargetBean {
private String userName;
// getter 和 setter 办法省掉
}
SourceBean source = new SourceBean();
source.setUsername("捡田螺的小男孩");
TargetBean target = new TargetBean();
BeanUtils.copyProperties(source, target);
System.out.println(target.getUserName()); // 输出为 null
在上述示例中,源方针SourceBean
的特点称号是username
,而方针方针TargetBean
的特点称号也是userName
。可是,两个 username
,一个N是大写,一个n是小写,即特点称号不一致,BeanUtils.copyProperties
办法无法主动映射这些特点(无法疏忽大小写主动匹配),因而方针方针的userName
特点值为null
。
咱们日常开发中,要留意这个坑哈~ 比方大小写不一致,差一两个字母等等。
第4个坑:Null 值掩盖
@Data
public class SourceBean {
private String name;
private String address;
}
@Data
public class TargetBean {
private String name;
private String address;
}
SourceBean source = new SourceBean();
source.setName("John");
source.setAddress(null);
TargetBean target = new TargetBean();
target.setAddress("田螺address");
BeanUtils.copyProperties(source, target);
System.out.println(target.getAddress()); // 输出为 null
在上述示例中,源方针 SourceBean 的 address 特点值为 null。默认情况下,BeanUtils.copyProperties 办法会将源方针中的 null 值特点掩盖到方针方针中。因而,方针方针的 address 特点值也为 null。
假如你不希望 null 值掩盖方针方针中的特点,能够运用 BeanUtils.copyProperties 办法的重载办法,并传入一个自定义的 ConvertUtilsBean 实例来进行装备。
第5个坑:留意引入的包
BeanUtils.copyProperties
其实有两个包,分别是spring、apache
。咱们留意一下哈,这两个包,是有点不一样的:
//org.springframework.beans.BeanUtils(源方针在左面,方针方针在右边)
public static void copyProperties(Object source, Object target) throws BeansException
//org.apache.commons.beanutils.BeanUtils(源方针在右边,方针方针在左面)
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException
咱们运用的时分,要留意一下哈,千万留意自己引入的哪个BeanUtils
,写对应参数位置。
第6个坑:Boolean类型数据+is特点最初的坑
把SourceBean和TargetBean
中的都有个特点isTianLuo
,它们的数据类型坚持不变,可是一个为基本类型boolean
,一个为包装类型Boolean
@Data
public class SourceBean {
private boolean isTianLuo;
}
@Data
public class TargetBean {
private Boolean isTianLuo;
}
跑测试用里的时分,发现赋值不上:
SourceBean source = new SourceBean();
source.setTianLuo(true);
TargetBean target = new TargetBean();
BeanUtils.copyProperties(source, target);
System.out.println(target.getIsTianLuo()); // 输出为 null
为什么呢?即使是一个包装类型,一个基本类型,应该能够赋值上才对的。
这是因为当特点类型为
boolean
时,特点名以is
最初,特点名会去掉前面的is
,因而源方针和方针方针特点对不上啦。
咱们运用BeanUtils.copyProperties
过程中,要留意哈~
第7个坑:查找不到字段引证
在某些开发场景呢,假如咱们要修正某个字段的赋值,咱们或许会全文查找它的一切set
办法,看哪些地方引证到。
可是呢,假如运用BeanUtils.copyProperties
,就不知道是否引证到对应的ste办法啦,即查找不到字段引证。这就或许导致你会漏掉修正对应的字段。
第8个坑:不同内部类,即使相同特点,也是赋值失利
@Data
public class CopySource {
public String outerName;
public CopySource.InnerClass innerClass;
@Data
public static class InnerClass {
public String InnerName;
}
}
@Data
public class CopyTarget {
public String outerName;
public CopyTarget.InnerClass innerClass;
@Data
public static class InnerClass {
public String InnerName;
}
}
CopySource test1 = new CopySource();
test1.outerName = "outTianluo";
CopySource.InnerClass innerClass = new CopySource.InnerClass();
innerClass.InnerName = "innerTianLuo";
test1.innerClass = innerClass;
System.out.println(test1);
CopyTarget test2 = new CopyTarget();
BeanUtils.copyProperties(test1, test2);
System.out.println(test2); //输出CopyTarget(outerName=outTianluo, innerClass=null)
以上demo
中,CopySource
和CopyTarget
各自存在一个内部类InnerClass
,虽然这个内部类特点也相同,类名也相同,可是在不同的类中,因而Spring
会认为特点不同,不会Copy
;
假如要仿制成功,能够让他们指向同一个内部类。
第9个坑:bean对应的特点,没有getter和setter办法,赋值失利
BeanUtils.copyProperties
要仿制特点值成功,需求对应的bean
要有getter和setter
办法。因为它是用反射拿到set和get办法再去拿特点值和设置特点值的。
@Data
public class SourceBean {
private String value;
}
@Getter //没有对应的setter办法
public class TargetBean {
private String value;
}
SourceBean source = new SourceBean();
source.setValue("捡田螺的小男孩");
TargetBean target = new TargetBean();
BeanUtils.copyProperties(source, target);
System.out.println(target.getValue()); //输出null
第10个坑:BeanUtils.copyProperties + 泛型
假如BeanUtils.copyProperties遇到泛型,也是很或许赋值失利的哈。咱们看下这个例子:
@Data
public class CopySource {
public String outerName;
public List<CopySource.InnerClass> clazz;
@Data
public static class InnerClass {
public String InnerName;
}
}
@ToString
@Data
public class CopyTarget {
public String outerName;
public List<CopyTarget.InnerClass> clazz;
@Data
public static class InnerClass {
public String InnerName;
}
}
CopySource test1 = new CopySource();
test1.outerName = "outTianluo";
CopySource.InnerClass innerClass = new CopySource.InnerClass();
innerClass.InnerName = "innerTianLuo";
List<CopySource.InnerClass> clazz = new ArrayList<>();
clazz.add(innerClass);
test1.setClazz(clazz);
System.out.println(test1);
CopyTarget test2 = new CopyTarget();
BeanUtils.copyProperties(test1, test2);
System.out.println(test2); //输出CopyTarget(outerName=outTianluo, clazz=null)
这里面的例子,BeanUtils.copyProperties
办法仿制包括泛型特点的方针clazz
。CopyTarget
和CopySource
的泛型特点类型不匹配,因而仿制赋值失利。
假如是低版本的包,泛型假如不匹配,则会报错,高本版则知识仿制赋值失利。
第11个坑:功能问题
因为这些BeanUtils
类都是选用反射机制完成的,对程序的效率也会有影响。我跑了个demo
对比:
SourceBean sourceBean = new SourceBean();
sourceBean.setName("tianLuoBoy");
TargetBean target = new TargetBean();
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) { //循环10万次
target.setName(sourceBean.getName());
}
System.out.println("common setter time:" + (System.currentTimeMillis() - beginTime));
long beginTime1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) { //循环10万次
BeanUtils.copyProperties(sourceBean, target);
}
System.out.println("bean copy time:" + (System.currentTimeMillis() - beginTime1));
//输出
common setter time:3
bean copy time:331
能够发现,简单的setter
和BeanUtils.copyProperties
对比,功能差距非常大。因而,慎用BeanUtils.copyProperties!!!
12. 替换BeanUtils.copyProperties的计划
以上聊了BeanUtils.copyProperties
的11个坑,都是在跟咱们聊,要慎用BeanUtils.copyProperties
。那有没有推荐替换它的计划呢。
第一种,那就是运用原始的setter和getter
办法。
运用手动的setter办法进行特点赋值。这种办法或许需求编写更多的代码,可是能够供给更细粒度的操控,并且在功能方面一般比BeanUtils.copyProperties更高效。
Target target = new Target();
target.setName(source.getName());
target.setAge(source.getAge());
假如实在方针bean
的特点比较多的话,能够运用插件GenerateAllSetter
,它能够一键生成方针的set
办法,挺方便的。
第二种计划,运用映射工具库,如MapStruct、ModelMapper
等,它们能够主动生成特点映射的代码。这些工具库能够削减手动编写setter办法的工作量,并供给更好的功能。
运用MapStruct
的示例:
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
@Mapping(source = "name", target = "name")
@Mapping(source = "age", target = "age")
Target mapToTarget(Source source);
}
Target target = SourceTargetMapper.INSTANCE.mapToTarget(source);
觉得有收成的伙伴,给个三连支持一下哈,感谢~