摘要:于是我建議這位小伙伴使用了進行屬性拷貝,這為我們的程序挖了一個坑阿里代碼規約當我們開啟阿里代碼掃描插件時,如果你使用了進行屬性拷貝,它會給你一個非常嚴重的警告。大名鼎鼎的提供的包,居然會存在性能問題,以致于阿里給出了嚴重的警告。
聲明:本文屬原創文章,始發于公號:程序員自學之道,并同步發布于 https://blog.csdn.net/dadiyang,特此,同步發布到 sf,轉載請注明出處。
緣起有一次開發過程中,剛好看到一個小伙伴在調用 set 方法,將一個數據庫中查詢出來的 PO 對象的屬性拷貝到 Vo 對象中,類似這樣:
可以看出,Po 和 Vo 兩個類的字段絕大部分是一樣的,我們一個個地調用 set 方法只是做了一些重復的冗長的操作。這種操作非常容易出錯,因為對象的屬性太多,有可能會漏掉一兩個,而且肉眼很難察覺。
類似這樣的操作,我們可以很容易想到,可以通過反射來解決。其實,如此普遍通用的功能,一個 BeanUtils 工具類就可以搞定了。
于是我建議這位小伙伴使用了 Apache BeanUtils.copyProperties 進行屬性拷貝,這為我們的程序挖了一個坑!
阿里代碼規約當我們開啟阿里代碼掃描插件時,如果你使用了 Apache BeanUtils.copyProperties 進行屬性拷貝,它會給你一個非常嚴重的警告。因為,Apache BeanUtils性能較差,可以使用 Spring BeanUtils 或者 Cglib BeanCopier 來代替。
看到這樣的警告,有點讓人有點不爽。大名鼎鼎的 Apache 提供的包,居然會存在性能問題,以致于阿里給出了嚴重的警告。
那么,這個性能問題究竟是有多嚴重呢?畢竟,在我們的應用場景中,如果只是很微小的性能損耗,但是能帶來非常大的便利性,還是可以接受的。
帶著這個問題。我們來做一個實驗,驗證一下。
如果對具體的測試方式沒有興趣,可以跳過直接看結果哦~
測試方法接口和實現定義首先,為了測試方便,讓我們來定義一個接口,并將幾種實現統一起來:
public interface PropertiesCopier { void copyProperties(Object source, Object target) throws Exception; } public class CglibBeanCopierPropertiesCopier implements PropertiesCopier { @Override public void copyProperties(Object source, Object target) throws Exception { BeanCopier copier = BeanCopier.create(source.getClass(), target.getClass(), false); copier.copy(source, target, null); } } // 全局靜態 BeanCopier,避免每次都生成新的對象 public class StaticCglibBeanCopierPropertiesCopier implements PropertiesCopier { private static BeanCopier copier = BeanCopier.create(Account.class, Account.class, false); @Override public void copyProperties(Object source, Object target) throws Exception { copier.copy(source, target, null); } } public class SpringBeanUtilsPropertiesCopier implements PropertiesCopier { @Override public void copyProperties(Object source, Object target) throws Exception { org.springframework.beans.BeanUtils.copyProperties(source, target); } } public class CommonsBeanUtilsPropertiesCopier implements PropertiesCopier { @Override public void copyProperties(Object source, Object target) throws Exception { org.apache.commons.beanutils.BeanUtils.copyProperties(target, source); } } public class CommonsPropertyUtilsPropertiesCopier implements PropertiesCopier { @Override public void copyProperties(Object source, Object target) throws Exception { org.apache.commons.beanutils.PropertyUtils.copyProperties(target, source); } }單元測試
然后寫一個參數化的單元測試:
@RunWith(Parameterized.class) public class PropertiesCopierTest { @Parameterized.Parameter(0) public PropertiesCopier propertiesCopier; // 測試次數 private static List測試結果testTimes = Arrays.asList(100, 1000, 10_000, 100_000, 1_000_000); // 測試結果以 markdown 表格的形式輸出 private static StringBuilder resultBuilder = new StringBuilder("|實現|100|1,000|10,000|100,000|1,000,000| ").append("|----|----|----|----|----|----| "); @Parameterized.Parameters public static Collection
時間單位毫秒
實現 | 100次 | 1,000次 | 10,000次 | 100,000次 | 1,000,000次 |
---|---|---|---|---|---|
StaticCglibBeanCopier | 0.055022 | 0.541029 | 0.999478 | 2.754824 | 9.88556 |
CglibBeanCopier | 5.320798 | 11.086323 | 61.037446 | 72.484607 | 333.384007 |
SpringBeanUtils | 5.180483 | 21.328542 | 30.021662 | 103.266375 | 966.439272 |
CommonsPropertyUtils | 9.729159 | 42.927356 | 74.063789 | 386.127787 | 1955.5437 |
CommonsBeanUtils | 24.99513 | 170.728558 | 572.335327 | 2970.3068 | 27563.3459 |
結果表明,Cglib 的 BeanCopier 的拷貝速度是最快的,即使是百萬次的拷貝也只需要 10 毫秒!
相比而言,最差的是 Commons 包的 BeanUtils.copyProperties 方法,100 次拷貝測試與表現最好的 Cglib 相差 400 倍之多。百萬次拷貝更是出現了 2800 倍的性能差異!
結果真是讓人大跌眼鏡。
但是它們為什么會有這么大的差異呢?
原因分析查看源碼,我們會發現 CommonsBeanUtils 主要有以下幾個耗時的地方:
輸出了大量的日志調試信息
重復的對象類型檢查
類型轉換
public void copyProperties(final Object dest, final Object orig) throws IllegalAccessException, InvocationTargetException { // 類型檢查 if (orig instanceof DynaBean) { ... } else if (orig instanceof Map) { ... } else { final PropertyDescriptor[] origDescriptors = ... for (PropertyDescriptor origDescriptor : origDescriptors) { ... // 這里每個屬性都調一次 copyProperty copyProperty(dest, name, value); } } } public void copyProperty(final Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException { ... // 這里又進行一次類型檢查 if (target instanceof DynaBean) { ... } ... // 需要將屬性轉換為目標類型 value = convertForCopy(value, type); ... } // 而這個 convert 方法在日志級別為 debug 的時候有很多的字符串拼接 publicT convert(final Class type, Object value) { if (log().isDebugEnabled()) { log().debug("Converting" + (value == null ? "" : " "" + toString(sourceType) + """) + " value "" + value + "" to type "" + toString(targetType) + """); } ... if (targetType.equals(String.class)) { return targetType.cast(convertToString(value)); } else if (targetType.equals(sourceType)) { if (log().isDebugEnabled()) { log().debug("No conversion required, value is already a " + toString(targetType)); } return targetType.cast(value); } else { // 這個 convertToType 方法里也需要做類型檢查 final Object result = convertToType(targetType, value); if (log().isDebugEnabled()) { log().debug("Converted to " + toString(targetType) + " value "" + result + """); } return targetType.cast(result); } }
具體的性能和源碼分析,可以參考這幾篇文章:
幾種copyProperties工具類性能比較:https://www.jianshu.com/p/bcb...
CGLIB中BeanCopier源碼實現:https://www.jianshu.com/p/f8b...
Java Bean Copy框架性能對比:https://yq.aliyun.com/article...
One more thing除了性能問題之外,在使用 CommonsBeanUtils 時還有其他的坑需要特別小心!
包裝類默認值在進行屬性拷貝時,雖然 CommonsBeanUtils 默認不會給原始包裝類賦默認值的,但是在使用低版本(1.8.0及以下)的時候,如果你的類有 Date 類型屬性,而且來源對象中該屬性值為 null 的話,就會發生異常:
org.apache.commons.beanutils.ConversionException: No value specified for "Date"
解決這個問題的辦法是注冊一個 DateConverter:
ConvertUtils.register(new DateConverter(null), java.util.Date.class);
然而這個語句,會導致包裝類型會被賦予原始類型的默認值,如 Integer 屬性默認賦值為 0,盡管你的來源對象該字段的值為 null。
在高版本(1.9.3)中,日期 null 值的問題和包裝類賦默認值的問題都被修復了。
這個在我們的包裝類屬性為 null 值時有特殊含義的場景,非常容易踩坑!例如搜索條件對象,一般 null 值表示該字段不做限制,而 0 表示該字段的值必須為0。
改用其他工具時當我們看到阿里的提示,或者你看了這篇文章之后,知道了 CommonsBeanUtils 的性能問題,想要改用 Spring 的 BeanUtils 時,要小心:
org.apache.commons.beanutils.BeanUtils.copyProperties(Object target, Object source); org.springframework.beans.BeanUtils.copyProperties(Object source, Object target);
從方法簽名上可以看出,這兩個工具類的名稱相同,方法名也相同,甚至連參數個數、類型、名稱都相同。但是參數的位置是相反的。因此,如果你想更改的時候,千萬要記得,將 target 和 source 兩個參數也調換過來!
另外,可能由于種種原因,你獲取的堆棧信息不完整找不到問題在哪,所以這里順便提醒一下:
如果你遇到 java.lang.IllegalArgumentException: Source must not be null 或者 java.lang.IllegalArgumentException: Target must not be null 這樣的異常信息卻到處找不到原因時,不用找了,這是由于你在 copyProperties 的時候傳了 null 值導致的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/74742.html
摘要:前言作為一名全干打字員,干活時經常會被要求使用各種各樣的語言去實現各種各樣的需求,來回切換起來寫的代碼就會或多或少有點不規范。今天我們以為例,講講在代碼中,我們需要注意的某些規范。 前言 作為一名全干打字員,干活時經常會被要求使用各種各樣的語言去實現各種各樣的需求,來回切換起來寫的代碼就會或多或少有點不規范。今天我們以JAVA為例,講講在代碼中,我們需要注意的某些規范。(本文標準依賴于...
摘要:拷貝操作又一個非常好用的工具類和中分別存在一個,提供了對。除了支持基本類型以及基本類型的數組之外,還支持這些類的對象,其余一概不支持。而且,由于這些類都是采用反射機制實現的,對程序的效率也會有影響。因此,慎用或者使用看效果如何 java bean拷貝操作又一個非常好用的工具類 BeanUitls :spring (org.springframework.beans.BeanUtils)...
摘要:說明這篇文章是我第一次認真閱讀阿里巴巴開發手冊終極版的筆記。說明本手冊明確防止是調用者的責任。一年半載后,那么單元測試幾乎處于廢棄狀態。好的單元測試能夠最大限度地規避線上故障。 說明 這篇文章是我第一次(認真)閱讀《阿里巴巴 Java 開發手冊(終極版)》的筆記。手冊本身對規范的講解已經非常詳細了,如果你已經有一定的開發經驗并且有良好的編碼習慣和意識,會發現大部分規范是符合常識的。所以...
摘要:熟悉和遵守阿里巴巴開發手冊的編程風格,那只是標,而代碼可讀性的本可以追溯到軟件設計階段。何為條設計規約是根據阿里巴巴實際項目架構經驗提煉而成,共條。本次新增的不單是條新的設計規約,還是千萬阿里人的技術之心。 摘要:2018年6月,《阿里巴巴Java開發手冊》再次刷新代碼規范認知,我們新增了16條設計規約!現免費開放下載,不可錯過!《阿里巴巴Java開發手冊》是阿里內部Java工程師所遵...
摘要:在中,工具類定義了一組公共方法,這篇文章將介紹中使用最頻繁及最通用的工具類。另外,工具類,根據阿里開發手冊,包名如果要使用不能帶,工具類命名為 在Java中,工具類定義了一組公共方法,這篇文章將介紹Java中使用最頻繁及最通用的Java工具類。以下工具類、方法按使用流行度排名,參考數據來源于Github上隨機選取的5萬個開源項目源碼。 一. org.apache.commons.io....
閱讀 1611·2021-11-22 09:34
閱讀 1695·2019-08-29 16:36
閱讀 2676·2019-08-29 15:43
閱讀 3119·2019-08-29 13:57
閱讀 1305·2019-08-28 18:05
閱讀 1881·2019-08-26 18:26
閱讀 3251·2019-08-26 10:39
閱讀 3466·2019-08-23 18:40