前言
做 JAVA 開發的同學都知道,在 JAVA 世界中萬事萬物皆為對象。是我們在實際開發中,經常會遇到將一個對象實例拷貝轉換為另一個對象實例的情況:對兩個對象的屬性進行淺(深)度複製。
舉個例子:
在 controller 中接收前端傳遞的參數的實體定義為以 Args結尾的類,然後我們在調用 service 層的接口,但是 service 中的接口參數是以 DTO 結尾的類型,這時候就需要將 Args 類對象屬性拷貝到 DTO 類對象上,由於對象的類型是不一樣的,所以我們需要編寫映射代碼將源對象的屬性值從一種類型轉換為另一種類型。
在具體介紹 BeanUtils 工具以前,先介紹一個有關拷貝的基礎知識。其實所有的 BeanUtils本質上就是對象拷貝工具,通常對象拷貝分為:深拷貝和淺拷貝,接下來做詳細解釋。
什麼是淺拷貝和深拷貝
在 JAVA 中除了 char、byte、short、int、long、float、double、boolean等基礎類型之外,還有類實例對象的引用數據類型。
一般使用"="做賦值操作,對於基本數據類型,實際上是拷貝的它的值,對於對象而言,賦值的實際上是這個對象的引用,將源對象的引用傳遞過去,本質上還是指向同一個對象。
通常說的淺拷貝和深拷貝就是在這個基礎上來做區分的。那麼什麼是深拷貝,什麼是淺拷貝呢?
如果在拷貝這個對象的時候,只對基本數據類型進行了拷貝,而對引用數據類型只是進行引用的傳遞,而沒有真實的創建一個新的對象,即為淺拷貝。
反之,在對引用數據類型進行拷貝的時候,創建了一個新的對象,並且複製其內的成員變量,即為深拷貝。
簡單來說:
淺拷貝:針對基本數據類型進行值傳遞,針對引用類型進行引用傳遞的拷貝即為淺拷貝深拷貝:針對基本數據類型進行值傳遞,針對引用數據類型,先創建新的對象實例,並複製其內容,即為深拷貝。
BeanUtils
前面介紹了關於拷貝的一些基礎知識,相信大家已經有所了解,下面就進行本文的主題,具體來分析一下Apache BeanUtils 和 Spring BeanUtils這兩種 BeanUtils 工具的實現和性能。
Apache 的 BeanUtils
先給出一個簡單的使用實例,代碼如下:
從上面的例子可以看出,BeanUtils工具的使用非常簡單,最常用的方法就是:
默認情況下,使用org.apache.commons.beanutils.BeanUtils對複雜對象是進行的淺拷貝,但是由於 Apache下的BeanUtils對象拷貝性能太差,不建議使用,這在阿里巴巴Java開發規約插件上也明確指出:
Ali-Check | 避免用Apache Beanutils進行屬性的copy。
到這裡你可能會疑惑,為什麼會出現拷貝性能差的情況呢?
因為在對對象進行拷貝的時候添加很多的校驗,比如像類型轉換,甚至還校驗了對象所屬類的訪問權限,可以說校驗是相當的複雜,這也就是造成性能差的根本原因,我們看下它的具體代碼實現:
Spring 的 BeanUtils
還是老規則,先給出使用實例:
從上面的實例可以看出上 Apache 的 BeanUtils 工具的使用方式基本一直,非常簡單。
Spring下的BeanUtils也是使用 copyProperties方法進行拷貝,不過實現的方式相對於前者要來得簡單得多了,可以說是非常的簡單,就是根據兩個對象屬性的名字進行匹配,做簡單的 get/set,僅檢查屬性的可訪問性。具體實現如下:
從上面的實現源碼可以看到:成員變量賦值是基於目標對象的成員列表,並且會跳過ignore以及在源對象中不存在的屬性,所以這個方法是安全的,不會因為兩個對象之間的結構差異導致錯誤,但是必須保證同名的兩個成員變量類型相同
小結
以上簡要的分析兩種BeanUtils的具體實現以及性能相關的分析,得出結論:Apache下的BeanUtils由於各種繁瑣的校驗以及可訪問性的校驗等等,導致性能較差,故實際開發中不建議使用,可以使用 Spring的BeanUtils ,或者使用其他拷貝框架進行替代,比如:Dozer、ModelMapper等等。