公司系統中之前一直有使用元件進行Bean copy的操作,只是知道此操作對效能有影響,但是到底有多少影響心裡一直沒有數。現在對Bean copy進行測試獲取量化的結果
目前Bean Copy的主流元件:
-
Apache BeanUtils
-
Spring BeanUtils
-
Cglib BeanCopier
眾所周知Apache BeanUtils效能太差,一般不推薦使用。這裡不對其進行測試,只測試後面兩個元件
測試環境:
- JAVA8
- Spring Boot 2.1.4.RELEASE
- 本地普通桌上型電腦
測試程式碼:
測試功能:迴圈N次,將TelAppModel 物件中的屬性複製到TelAppDto中,統計每種元件花費的時間,花費時間越少的效能越強。
測試POJO類 源類 TelAppModel.java 和目標類 TelAppDto.java,兩個類都是簡單的pojo類且成員變數相同。
普通的Java set/get方法實現Bean Copy, 程式碼如下:
public static void copySetGet(TelAppModel source, TelAppDto target){
target.setId(source.getId());
target.setTelPowerSavingMode((byte)0);
target.setTelSecret(source.getTelSecret());
target.setTelAppId(source.getTelAppId());
target.setTelName(source.getTelName());
target.setDesc(source.getDesc());
}
複製程式碼
Spring BeanUtils實現Bean Copy程式碼如下:
public static void copyPropertiesSpring(Object source, Object target){
BeanUtils.copyProperties(source, target);
}
複製程式碼
Cglib BeanCopier實現Bean Copy程式碼如下:
public static void copyPropertiesCglib(Object source, Object target){
BeanCopier beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
beanCopier.copy(source, target, null);
}
複製程式碼
測試程式碼: longCount:定義複製執行的次數
// 在執行Bean copy前 先初始化 longCount 個TelAppModel,做為測試素材
for(int i = 0; i < longCount; i++){
appModelSourceList.add(createModel());
}
複製程式碼
3種Bean copy方法依次呼叫此test()方法:
// set/get方法
test((a,b) -> copySetGet(a, b));
// spring BeanUtils
test((a,b) -> copyPropertiesSpring(a, b));
// spring BeanCopier
test((a,b) -> copyPropertiesCglib(a, b));
複製程式碼
// 此方法依次將列表中TelAppModel物件複製到TelAppDto物件中,並列印執行longCount次花費的時間,為了保證結果準確,以上操作執行3次,即3*longCount次
private void test(BiConsumer<TelAppModel, TelAppDto> biConsumer){
int runNum = 3;
for(int k = 0; k < runNum; k++) {
long loopCount = longCount;
long begin = System.currentTimeMillis();
for (int i = 0; i < loopCount; i++) {
TelAppModel telAppModel1 = appModelSourceList.get(i);
TelAppDto telAppDto = new TelAppDto();
biConsumer.accept(telAppModel1, telAppDto);
}
System.out.println((System.currentTimeMillis() - begin));
}
}
複製程式碼
測試報告:
分別執行1000、10000、100000、1000000次耗時數(毫秒): 詳細時間如下:
資料分析:- ○ set/get方法理論上應該是最快的
- ○ 效能如下: set/get方法 > Sprign BeanUtils > Cglib BeanCopier
- ○ Spring BeanUtils每次第一次迴圈花費時間特別多,和後面二次不在一個資料量,資料有異常
在執行命令時,通過jvisualvm檢視CPU的耗時時間,詳細如下:
圖表分析:- Spring BeanUtils第一次執行CachedIntrospectionResults.forClass()方法時,會將class資訊從磁碟載入到記憶體快取,然後後續的操作會從快取中獲取class資訊。這也解釋了為什麼Spring BeanUtils為什麼會比較慢的原因。另外此操作是同步的,如果多執行緒同時載入相同的class,會出現阻塞的情況。
- Spring BeanUtils的copyProperties佔用的CPU也比較多,說明真正執行copy Bean操作依然會花費較多的時間
- 觀察上圖,發現BeanCopier主要花費的時間是呼叫create()方法,真正執行copy Bean花費的時間較少,現在我們根據此進行優化,執行Bean Copy時,不是每次執行建立新BeanCopier,而是使用將BeanCopier.create()建立的BeanCopier進行快取,修改後程式碼如下:
static BeanCopier beanCopierStatic = BeanCopier.create(TelAppModel.class, TelAppDto.class, false);
public static void copyPropertiesCglibStatic(Object source, Object target){
beanCopierStatic.copy(source, target, null);
}
複製程式碼
再分別執行1000、10000、100000、1000000次耗時數(毫秒): 詳細時間如下:
分析: Cglib BeanCopier優化,效能大大提高。cglib在效能和set/get方法相差不大
結論:
spring beanUtils 和 cglib 效能都還可以接受,如果對效能沒有非常苛刻的要求,使用cglib或spring bean utils 問題都問題不大,推薦優先使用cglib
複製程式碼