在實際業務開發中,我們經常會遇到 VO、BO、PO、DTO 等物件屬性之間的賦值,當屬性較多時,我們使用 get、set 來賦值比較大量的工作,所以很多人會選擇使用 Spring 提供的複製工具 Beanutils 的 copyproperties 方法,完成物件間屬性的複製。 這樣一來,我們可以大大減少手動編寫物件屬性賦值所需的工作量,那麼既然這麼方便,為什麼不建議使用呢?下面我們來看看 beanutilscopyproperties 資料複製一些常見的坑
這個坑可以細分為以下兩種型別:
1)同一屬性的型別不同。
在實際開發中,很有可能同乙個欄位的型別被定義在不同的類中,比如id,A類中定義的型別可能是long,B類中定義的型別是string,在這種情況下,如果beanutilscopy屬性,複製會失敗,導致對應的字段為 null,對應的情況如下:
public class beanutilstest }@data@allargsconstructorclass sourcepojo@dataclass targetpojo
對應的結果如下:
可以看到,由於型別不一致,複製的 id 字段值為 null。
2)同一字段分別使用包型別和基本型別。
如果對欄位分別使用包裝類和基型別,則在未傳遞實際值時將發生異常,如以下示例所示:
public class beanutilstest }@dataclass sourcepojo@dataclass targetpojo
在測試用例中,id 字段分別使用複製源和複製目標中的包裝型別和基型別,您可以看到在下面的複製過程中發生了異常。
注意:如果 Boolean 屬性使用基型別和包裝型別,並且屬性名稱以 is 開頭,例如 issuccess,則複製將失敗。
在業務開發中,我們可能需要複製一些字段,如果複製的資料中的某些欄位有空值,但是需要複製的相同字段對應的值不是空,如果直接使用 beanutilsCopyProperties,則複製資料的 null 值將覆蓋複製資料的字段,導致原始資料失效。
對應案例如下:
public class beanutilstest }@dataclass sourcepojo@dataclass targetpojo
對應的結果如下:
您可以看到,最初在複製目標結果中具有值的使用者名字段被 null 覆蓋。 雖然你可以使用 beanutilsCopyProperties,帶有自定義的 convertutilsbean 來複製一些字段,但這也比較複雜,它失去了 beanutils 的使用。copyproperties 對複製資料很有意義,因此不建議這樣做。
使用 beanutils 時當使用 copyproperties 複製資料時,如果專案中同時引入了 Spring 的 bean 包和 Apache 的 beanutils 包,如果包匯入錯誤,很可能導致資料複製失敗,排查時不容易找到。 我們通常在 sping 包中使用 copy 方法,兩者的區別如下:
源物件在左邊,目標物件在右邊)public static void copyproperties(object source, object target) 丟擲 beansexception 源物件在右邊,目標物件在左邊)public static void copyproperties(object dest, object orig) throws illegalaccessexception, invocationtargetexception
在開發或故障排除過程中,如果我們在鏈結中查詢字段值 (來電者沒有通過它我們可以使用全文搜尋來查詢其對應的賦值方法(如設定模式、構建模式等),但是如果在鏈結中使用了beanutils.copy屬性,則難以快速定位賦值位置,導致故障排除效率低下。
內部類資料無法正常複製,即使型別和欄位名稱相同也無法複製成功,如下圖所示
公共類 beanutilstest }以下是類資訊,直接顯示在乙個piece@data@tostringpublic類 sourcepojo} 中@data@tostringpublic類 targetpojo}
其工作原理如下:
在上面的例子中,複製源和複製目標中有乙個內部類 innerclass,雖然內部類屬性相同,類名相同,但是在不同的類中,所以 Spring 會認為屬性不同,所以資料不會被複製。
在這裡,我將首先回顧一下深拷貝和精簡拷貝。
淺拷貝指建立乙個新物件,該物件與原始物件具有相同的屬性值,但仍共享引用型別的屬性的相同引用。 也就是說,在淺拷貝中,當原始內容的引用屬性值發生變化時,複製物件的引用屬性值也會發生變化。
深拷貝指建立具有與原始物件相同的屬性值的新物件,包括引用型別的屬性。 深層複製以遞迴方式複製引用的物件,從而建立乙個全新的物件,因此深層複製複製的物件與原始物件完全分開。
下面是它可能看起來像的示例:
public class beanutilstest }@data@allargsconstructorclass card @noargsconstructor@allargsconstructor@dataclass person
其工作原理如下:
總結:通過執行結果可以發現,複製後一旦修改了原物件的引用型別資料,就會導致複製資料的值異常,這也是難以排查的。
beanutils.copyproperties底層是通過反射獲取物件的set和get方法,然後通過get和set完成資料的複製,在整體複製中效率較低。
使用方法如下beanutils.copyproperties為了更容易直觀地看到效果,這裡以複製 10,000 次為例
public class beanutilstest system.out.println("複製方法:"+(system.currenttimemillis()-copystarttime));long setstarttime = system.currenttimemillis();for(int i = 0; i < 10000; i++)system.out.println("set 方法:"+(system.currenttimemillis()-setstarttime));data@allargsconstructor@noargsconstructorclass user
以下是執行效率結果的比較:
可以發現,常規集和 beanutilsCopyProperties,效能差距非常大。 因此,請使用 beanutilscopyproperties。
這就是使用 beanutils 的全部內容這些陷阱大多比較隱蔽,排查問題並不容易,所以不建議使用 beanutilscopy屬性。 歡迎對文章中的不足之處進行補充和糾正。
作者:京東科技 孫揚偉.*:京東雲開發者社群 **請註明**。