Java中BeanUtils.copyProperties的11個(gè)坑總結(jié)
第1個(gè)坑: 類型不匹配
@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
中,源對(duì)象SourceBean
的age
屬性是一個(gè)Long
類型,而目標(biāo)對(duì)象TargetBean
的age
屬性是一個(gè)String
類型。由于類型不匹配,BeanUtils.copyProperties
不會(huì)賦值成功的。我跑demo
的結(jié)果,控制臺(tái)輸出null
。
第2個(gè)坑: BeanUtils.copyProperties是淺拷貝
先給大家復(fù)習(xí)一下,什么是深拷貝?什么是淺拷貝?
- 淺拷貝是指創(chuàng)建一個(gè)新對(duì)象,該對(duì)象的屬性值與原始對(duì)象相同,但對(duì)于引用類型的屬性,仍然共享相同的引用。換句話說(shuō),淺拷貝只復(fù)制對(duì)象及其引用,而不復(fù)制引用指向的對(duì)象本身。
- 深拷貝是指創(chuàng)建一個(gè)新對(duì)象,該對(duì)象的屬性值與原始對(duì)象相同,包括引用類型的屬性。深拷貝會(huì)遞歸復(fù)制引用對(duì)象,創(chuàng)建全新的對(duì)象,以確??截惡蟮膶?duì)象與原始對(duì)象完全獨(dú)立。
我再給個(gè)代碼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"
在上述示例中,源對(duì)象Person
的屬性address
是一個(gè)引用類型。當(dāng)使用BeanUtils.copyProperties
方法進(jìn)行屬性復(fù)制時(shí),實(shí)際上只復(fù)制了引用,即目標(biāo)對(duì)象targetPerson
的 address
屬性引用和源對(duì)象 sourcePerson
的 address
屬性引用指向同一個(gè)對(duì)象。因此,當(dāng)修改源對(duì)象的address
對(duì)象時(shí),目標(biāo)對(duì)象的address
對(duì)象也會(huì)被修改。
大家日常開(kāi)發(fā)中,要注意這個(gè)坑哈~
第3個(gè)坑:屬性名稱不一致
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
在上述示例中,源對(duì)象SourceBean
的屬性名稱是username
,而目標(biāo)對(duì)象TargetBean
的屬性名稱也是userName
。但是,兩個(gè) username
,一個(gè)N是大寫(xiě),一個(gè)n是小寫(xiě),即屬性名稱不一致,BeanUtils.copyProperties
方法無(wú)法自動(dòng)映射這些屬性(無(wú)法忽略大小寫(xiě)自動(dòng)匹配),因此目標(biāo)對(duì)象的userName
屬性值為null
。
大家日常開(kāi)發(fā)中,要注意這個(gè)坑哈~ 比如大小寫(xiě)不一致,差一兩個(gè)字母等等。
第4個(gè)坑: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
在上述示例中,源對(duì)象 SourceBean 的 address 屬性值為 null。默認(rèn)情況下,BeanUtils.copyProperties 方法會(huì)將源對(duì)象中的 null 值屬性覆蓋到目標(biāo)對(duì)象中。因此,目標(biāo)對(duì)象的 address 屬性值也為 null。
如果你不希望 null 值覆蓋目標(biāo)對(duì)象中的屬性,可以使用 BeanUtils.copyProperties 方法的重載方法,并傳入一個(gè)自定義的 ConvertUtilsBean 實(shí)例來(lái)進(jìn)行配置。
第5個(gè)坑:注意引入的包
BeanUtils.copyProperties
其實(shí)有兩個(gè)包,分別是spring、apache
。大家注意一下哈,這兩個(gè)包,是有點(diǎn)不一樣的:
//org.springframework.beans.BeanUtils(源對(duì)象在左邊,目標(biāo)對(duì)象在右邊) public static void copyProperties(Object source, Object target) throws BeansException //org.apache.commons.beanutils.BeanUtils(源對(duì)象在右邊,目標(biāo)對(duì)象在左邊) public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException
大家使用的時(shí)候,要注意一下哈,千萬(wàn)注意自己引入的哪個(gè)BeanUtils
,寫(xiě)對(duì)應(yīng)參數(shù)位置。
第6個(gè)坑:Boolean類型數(shù)據(jù)+is屬性開(kāi)頭的坑
把SourceBean和TargetBean
中的都有個(gè)屬性isTianLuo
,它們的數(shù)據(jù)類型保持不變,但是一個(gè)為基本類型boolean
,一個(gè)為包裝類型Boolean
@Data public class SourceBean { private boolean isTianLuo; } @Data public class TargetBean { private Boolean isTianLuo; }
跑測(cè)試用里的時(shí)候,發(fā)現(xiàn)賦值不上:
SourceBean source = new SourceBean(); source.setTianLuo(true); TargetBean target = new TargetBean(); BeanUtils.copyProperties(source, target); System.out.println(target.getIsTianLuo()); // 輸出為 null
為什么呢?即使是一個(gè)包裝類型,一個(gè)基本類型,應(yīng)該可以賦值上才對(duì)的。
這是因?yàn)楫?dāng)屬性類型為
boolean
時(shí),屬性名以is
開(kāi)頭,屬性名會(huì)去掉前面的is
,因此源對(duì)象和目標(biāo)對(duì)象屬性對(duì)不上啦。
大家使用BeanUtils.copyProperties
過(guò)程中,要注意哈~
第7個(gè)坑:查找不到字段引用
在某些開(kāi)發(fā)場(chǎng)景呢,如果我們要修改某個(gè)字段的賦值,我們可能會(huì)全文搜索它的所有set
方法,看哪些地方引用到。
但是呢,如果使用BeanUtils.copyProperties
,就不知道是否引用到對(duì)應(yīng)的ste方法啦,即查找不到字段引用。這就可能導(dǎo)致你會(huì)漏掉修改對(duì)應(yīng)的字段。
第8個(gè)坑:不同內(nèi)部類,即使相同屬性,也是賦值失敗
@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
各自存在一個(gè)內(nèi)部類InnerClass
,雖然這個(gè)內(nèi)部類屬性也相同,類名也相同,但是在不同的類中,因此Spring
會(huì)認(rèn)為屬性不同,不會(huì)Copy
;
如果要復(fù)制成功,可以讓他們指向同一個(gè)內(nèi)部類。
第9個(gè)坑:bean對(duì)應(yīng)的屬性,沒(méi)有g(shù)etter和setter方法,賦值失敗
BeanUtils.copyProperties
要拷貝屬性值成功,需要對(duì)應(yīng)的bean
要有getter和setter
方法。因?yàn)樗怯梅瓷淠玫絪et和get方法再去拿屬性值和設(shè)置屬性值的。
@Data public class SourceBean { private String value; } @Getter //沒(méi)有對(duì)應(yīng)的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個(gè)坑:BeanUtils.copyProperties + 泛型
如果BeanUtils.copyProperties遇到泛型,也是很可能賦值失敗的哈。大家看下這個(gè)例子:
@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
方法拷貝包含泛型屬性的對(duì)象clazz
。CopyTarget
和CopySource
的泛型屬性類型不匹配,因此拷貝賦值失敗。
如果是低版本的包,泛型如果不匹配,則會(huì)報(bào)錯(cuò),高本版則知識(shí)拷貝賦值失敗。
第11個(gè)坑:性能問(wèn)題
由于這些BeanUtils
類都是采用反射機(jī)制實(shí)現(xiàn)的,對(duì)程序的效率也會(huì)有影響。我跑了個(gè)demo
對(duì)比:
SourceBean sourceBean = new SourceBean(); sourceBean.setName("tianLuoBoy"); TargetBean target = new TargetBean(); long beginTime = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { //循環(huán)10萬(wàn)次 target.setName(sourceBean.getName()); } System.out.println("common setter time:" + (System.currentTimeMillis() - beginTime)); long beginTime1 = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { //循環(huán)10萬(wàn)次 BeanUtils.copyProperties(sourceBean, target); } System.out.println("bean copy time:" + (System.currentTimeMillis() - beginTime1)); //輸出 common setter time:3 bean copy time:331
可以發(fā)現(xiàn),簡(jiǎn)單的setter
和BeanUtils.copyProperties
對(duì)比,性能差距非常大。因此,慎用BeanUtils.copyProperties?。?!
12. 替換BeanUtils.copyProperties的方案
以上聊了BeanUtils.copyProperties
的11個(gè)坑,都是在跟大家聊,要慎用BeanUtils.copyProperties
。那有沒(méi)有推薦替換它的方案呢。
第一種,那就是使用原始的setter和getter
方法。
使用手動(dòng)的setter方法進(jìn)行屬性賦值。這種方法可能需要編寫(xiě)更多的代碼,但是可以提供更細(xì)粒度的控制,并且在性能方面通常比BeanUtils.copyProperties更高效。
Target target = new Target(); target.setName(source.getName()); target.setAge(source.getAge());
如果實(shí)在對(duì)象bean
的屬性比較多的話,可以使用插件GenerateAllSetter
,它可以一鍵生成對(duì)象的set
方法,挺方便的。
第二種方案,使用映射工具庫(kù),如MapStruct、ModelMapper
等,它們可以自動(dòng)生成屬性映射的代碼。這些工具庫(kù)可以減少手動(dòng)編寫(xiě)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);
以上就是Java中BeanUtils.copyProperties容易遇到的11個(gè)坑總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Java BeanUtils.copyProperties坑的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java代碼關(guān)閉tomcat程序及出現(xiàn)問(wèn)題解析
這篇文章主要介紹了java代碼關(guān)閉tomcat程序 及出現(xiàn)問(wèn)題解析,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2019-05-05詳解SpringCloud Ribbon 負(fù)載均衡通過(guò)服務(wù)器名無(wú)法連接的神坑
這篇文章主要介紹了詳解SpringCloud Ribbon 負(fù)載均衡通過(guò)服務(wù)器名無(wú)法連接的神坑,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-06-06SpringBoot文件上傳接口并發(fā)性能調(diào)優(yōu)
在一個(gè)項(xiàng)目現(xiàn)場(chǎng),文件上傳接口(文件500K)QPS只有30,這個(gè)并發(fā)性能確實(shí)堪憂,此文記錄出坑過(guò)程,文中通過(guò)代碼示例講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-06-06MyBatis一對(duì)一級(jí)聯(lián)更新問(wèn)題小結(jié)
日常工作中經(jīng)常會(huì)涉及到一對(duì)一級(jí)聯(lián)更新的問(wèn)題,本文主要介紹了MyBatis一對(duì)一級(jí)聯(lián)更新問(wèn)題小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01Java8新特性之再見(jiàn)Permgen_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java8新特性之再見(jiàn)Permgen的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-06-06Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器代碼解析
這篇文章主要介紹了Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12