自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么要避免用Apache Beanutils進(jìn)行屬性的copy

開發(fā) 前端
在實(shí)際的項(xiàng)目開發(fā)中,對象間賦值普遍存在,隨著雙十一、秒殺等電商過程愈加復(fù)雜,數(shù)據(jù)量也在不斷攀升,效率問題,浮出水面。

大家好,我是哪吒。

今天,通過代碼實(shí)例、源碼解讀、四大工具類橫向?qū)Ρ鹊姆绞?,和大家一起聊一聊對象賦值的問題。

在實(shí)際的項(xiàng)目開發(fā)中,對象間賦值普遍存在,隨著雙十一、秒殺等電商過程愈加復(fù)雜,數(shù)據(jù)量也在不斷攀升,效率問題,浮出水面。

問:如果是你來寫對象間賦值的代碼,你會怎么做?

答:想都不用想,直接代碼走起來,get、set即可。

問:下圖這樣?

圖片

答:對啊,你怎么能把我的代碼放到網(wǎng)上?

問:沒,我只是舉個例子

答:這涉及到商業(yè)機(jī)密,是很嚴(yán)重的問題

問:我發(fā)現(xiàn)你挺能扯皮啊,直接回答問題行嗎?

答:OK,OK,我也覺得這樣寫很low,上次這么寫之后,差點(diǎn)挨打

  1. 對象太多,ctrl c + strl v,鍵盤差點(diǎn)沒敲壞;
  2. 而且很容易出錯,一不留神,屬性沒對應(yīng)上,賦錯值了;
  3. 代碼看起來很傻缺,一個類好幾千行,全是get、set復(fù)制,還起個了自以為很優(yōu)雅的名字transfer;
  4. 如果屬性名不能見名知意,還得加上每個屬性的含義注釋(基本這種賦值操作,都是要加的,注釋很重要,注釋很重要,注釋很重要);
  5. 代碼維護(hù)起來很麻煩;
  6. 如果對象過多,會產(chǎn)生類爆炸問題,如果屬性過多,會嚴(yán)重違背阿里巴巴代碼規(guī)約(一個方法的實(shí)際代碼最多20行);

問:行了,行了,說說,怎么解決吧。

答:很簡單啊,可以通過工具類Beanutils直接賦值啊

問:我聽說工具類最近很卷,你用的哪個?。?/h4>

答:就Apache自帶的那個啊,賊簡單。我手寫一個,給你欣賞一下。

圖片

問:你這代碼報錯啊,避免用Apache Beanutils進(jìn)行屬性的copy。

答:沒報錯,只是嚴(yán)重警告而已,代碼能跑就行,有問題再優(yōu)化唄

問:你這什么態(tài)度?人事在哪劃拉的人,為啥會出現(xiàn)嚴(yán)重警告?

答:拿多少錢,干多少活,我又不是XXX,應(yīng)該是性能問題吧

問:具體什么原因?qū)е碌哪兀?/p>

答:3000塊錢還得手撕一下 apache copyProperties 的源代碼唄?

通過單例模式調(diào)用copyProperties,但是,每一個方法對應(yīng)一個BeanUtilsBean.getInstance()實(shí)例,每一個類實(shí)例對應(yīng)一個實(shí)例,這不算一個真正的單例模式。

public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}

性能瓶頸 --> 日志太多也是病

通過源碼可以看到,每一個copyProperties都要進(jìn)行多次類型檢查,還要打印日志。

public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
// 類型檢查
if (dest == null) {
throw new IllegalArgumentException("No destination bean specified");
} else if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
} else {
// 打印日志
if (this.log.isDebugEnabled()) {
this.log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");
}

int var5;
int var6;
String name;
Object value;
// 類型檢查
// DanyBean 提供了可以動態(tài)修改實(shí)現(xiàn)他的類的屬性名稱、屬性值、屬性類型的功能
if (orig instanceof DynaBean) {
// 獲取源對象所有屬性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;

for(var6 = 0; var6 < var5; ++var6) {
DynaProperty origDescriptor = var4[var6];
// 獲取源對象屬性名
name = origDescriptor.getName();
// 判斷源對象是否可讀、判斷目標(biāo)對象是否可寫
if (this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
// 獲取對應(yīng)的值
value = ((DynaBean)orig).get(name);
// 每個屬性都調(diào)用一次copyProperty
this.copyProperty(dest, name, value);
}
}
} else if (orig instanceof Map) {
...
} else {
...
}

}
}

通過 jvisualvm.exe 檢測代碼性能

再通過jvisualvm.exe檢測一下運(yùn)行情況,果然,logging.log4j赫然在列,穩(wěn)居耗時Top1。

圖片

問:還有其它好的方式嗎?性能好一點(diǎn)的

答:當(dāng)然有,據(jù)我了解有 4 種工具類,實(shí)際上,可能會有更多,話不多說,先簡單介紹一下。

  1. org.apache.commons.beanutils.BeanUtils。
  2. org.apache.commons.beanutils.PropertyUtils。
  3. org.springframework.cglib.beans.BeanCopier。
  4. org.springframework.beans.BeanUtils。

問:那你怎么不用?

答:OK,我來演示一下

public class Test {

private static void apacheBeanUtilsCopyTest(User source, User target, int sum){
for (int i = 0; i < sum; i++) {
org.apache.commons.beanutils.BeanUtils.copyProperties(source, target);
}
}

private static void commonsPropertyCopyTest(User source, User target, int sum){
for (int i = 0; i < sum; i++) {
org.apache.commons.beanutils.PropertyUtils.copyProperties(target, source);
}
}

static BeanCopier copier = BeanCopier.create(User.class, User.class, false);
private static void cglibBeanCopyTest(User source, User target, int sum){
for (int i = 0; i < sum; i++) {
org.springframework.cglib.beans.BeanCopier.copier.copy(source, target, null);
}
}

private static void springBeanCopy(User source, User target, int sum){
for (int i = 0; i < sum; i++) {
org.springframework.beans.BeanUtils.copyProperties(source, target);
}
}
}

"四大金剛" 性能統(tǒng)計

方法

1000

10000

100000

1000000

apache BeanUtils

906毫秒

807毫秒

1892毫秒

11049毫秒

apache PropertyUtils

17毫秒

96毫秒

648毫秒

5896毫秒

spring cglib BeanCopier

0毫秒

1毫秒

3毫秒

10毫秒

spring copyProperties

87毫秒

90毫秒

123毫秒

482毫秒

不測不知道,一測嚇一跳,差的還真的多。

spring cglib BeanCopier性能最好,apache BeanUtils性能最差。

性能走勢 --> spring cglib BeanCopier 優(yōu)于 spring copyProperties 優(yōu)于 apache PropertyUtils 優(yōu)于 apache BeanUtils

避免用Apache Beanutils進(jìn)行屬性的copy的問題 上面分析完了,下面再看看其它的方法做了哪些優(yōu)化。

Apache PropertyUtils 源碼分析?

從源碼可以清晰的看到,類型檢查變成了非空校驗(yàn),去掉了每一次copy的日志記錄,性能肯定更好了。

  1. 類型檢查變成了非空校驗(yàn)
  2. 去掉了每一次copy的日志記錄
  3. 實(shí)際賦值的地方由copyProperty變成了DanyBean  + setSimpleProperty;

DanyBean 提供了可以動態(tài)修改實(shí)現(xiàn)他的類的屬性名稱、屬性值、屬性類型的功能。

public void copyProperties(Object dest, Object orig){
// 判斷數(shù)據(jù)源和目標(biāo)對象不是null
if (dest == null) {
throw new IllegalArgumentException("No destination bean specified");
} else if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
} else {
// 刪除了org.apache.commons.beanutils.BeanUtils.copyProperties中最為耗時的log日志記錄
int var5;
int var6;
String name;
Object value;
// 類型檢查
if (orig instanceof DynaBean) {
// 獲取源對象所有屬性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;

for(var6 = 0; var6 < var5; ++var6) {
DynaProperty origDescriptor = var4[var6];
// 獲取源對象屬性名
name = origDescriptor.getName();
// 判斷源對象是否可讀、判斷目標(biāo)對象是否可寫
if (this.isReadable(orig, name) && this.isWriteable(dest, name)) {
// 獲取對應(yīng)的值
value = ((DynaBean)orig).get(name);
// 相對于org.apache.commons.beanutils.BeanUtils.copyProperties此處有優(yōu)化
// DanyBean 提供了可以動態(tài)修改實(shí)現(xiàn)他的類的屬性名稱、屬性值、屬性類型的功能
if (dest instanceof DynaBean) {
((DynaBean)dest).set(name, value);
} else {
// 每個屬性都調(diào)用一次copyProperty
this.setSimpleProperty(dest, name, value);
}
}
}
} else if (orig instanceof Map) {
...
} else {
...
}

}
}

通過 jvisualvm.exe 檢測代碼性能

再通過jvisualvm.exe檢測一下運(yùn)行情況,果然,logging.log4j沒有了,其他的基本不變。

Spring copyProperties 源碼分析?

  1. 判斷數(shù)據(jù)源和目標(biāo)對象的非空判斷改為了斷言。
  2. 每次copy沒有日志記錄。
  3. 沒有if (orig instanceof DynaBean) {這個類型檢查。
  4. 增加了放開權(quán)限的步驟。
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties){

// 判斷數(shù)據(jù)源和目標(biāo)對象不是null
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");

/**
* 若target設(shè)置了泛型,則默認(rèn)使用泛型
* 若是 editable 是 null,則此處忽略
* 一般情況下editable都默認(rèn)為null
*/
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}

// 獲取target中全部的屬性描述
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
// 需要忽略的屬性
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
// 目標(biāo)對象存在寫入方法、屬性不被忽略
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
/**
* 源對象存在讀取方法、數(shù)據(jù)是可復(fù)制的
* writeMethod.getParameterTypes()[0]:獲取 writeMethod 的第一個入?yún)㈩愋?/span>
* readMethod.getReturnType():獲取 readMethod 的返回值類型
* 判斷返回值類型和入?yún)㈩愋褪欠翊嬖诶^承關(guān)系,只有是繼承關(guān)系或相等的情況下,才會進(jìn)行注入
*/
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
// 放開讀取方法的權(quán)限
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
// 通過反射獲取值
Object value = readMethod.invoke(source);
// 放開寫入方法的權(quán)限
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
// 通過反射寫入值
writeMethod.invoke(target, value);
}
}
}
}
}

總結(jié)?

阿里的友情提示,避免用Apache Beanutils進(jìn)行對象的copy,還是很有道理的。

Apache Beanutils 的性能問題出現(xiàn)在類型校驗(yàn)和每一次copy的日志記錄。

Apache PropertyUtils 進(jìn)行了如下優(yōu)化:

  1. 類型檢查變成了非空校驗(yàn)。
  2. 去掉了每一次copy的日志記錄。
  3. 實(shí)際賦值的地方由copyProperty變成了DanyBean  + setSimpleProperty。

Spring copyProperties 進(jìn)行了如下優(yōu)化:

  1. 判斷數(shù)據(jù)源和目標(biāo)對象的非空判斷改為了斷言。
  2. 每次copy沒有日志記錄。
  3. 沒有if (orig instanceof DynaBean) {這個類型檢查。
  4. 增加了放開權(quán)限的步驟。

本文轉(zhuǎn)載自微信公眾號「哪吒編程」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系哪吒編程公眾號。

責(zé)任編輯:姜華 來源: 哪吒編程
相關(guān)推薦

2020-09-08 16:25:18

Apache BeancopyJava

2020-07-30 12:16:33

阿里巴巴Apache對象

2022-12-26 00:25:06

2022-05-10 15:24:34

KafkaZooKeeperKafka Raft

2023-09-21 16:17:48

數(shù)據(jù)驅(qū)動運(yùn)營

2022-01-03 08:06:15

函數(shù)Go數(shù)據(jù)

2024-06-04 00:10:00

開發(fā)拷貝

2024-02-17 21:57:10

2015-08-06 10:14:15

造輪子facebook

2022-08-15 08:27:02

基站網(wǎng)絡(luò)

2013-03-12 14:30:09

Ubuntu操作系統(tǒng)

2014-05-19 15:52:57

Apache StraApache

2017-08-02 16:47:43

數(shù)據(jù)數(shù)據(jù)收集數(shù)據(jù)分析

2017-09-08 08:35:16

Android代碼API設(shè)計

2024-10-17 16:41:57

KafkaZooKeeper

2019-01-14 07:28:56

大數(shù)據(jù)云計算互聯(lián)網(wǎng)

2014-08-25 10:00:18

開源

2015-05-12 11:04:42

Java EE學(xué)習(xí)Java EE

2019-03-19 08:59:13

物聯(lián)網(wǎng)IOT技術(shù)

2019-11-27 10:25:15

SaaS云端IT架構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號