新聞中心
大家好,我是哪吒。

成都創(chuàng)新互聯(lián)公司專(zhuān)注為客戶(hù)提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站建設(shè)、做網(wǎng)站、貴德網(wǎng)絡(luò)推廣、小程序定制開(kāi)發(fā)、貴德網(wǎng)絡(luò)營(yíng)銷(xiāo)、貴德企業(yè)策劃、貴德品牌公關(guān)、搜索引擎seo、人物專(zhuān)訪(fǎng)、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供貴德建站搭建服務(wù),24小時(shí)服務(wù)熱線(xiàn):18980820575,官方網(wǎng)址:www.cdcxhl.com
今天,通過(guò)代碼實(shí)例、源碼解讀、四大工具類(lèi)橫向?qū)Ρ鹊姆绞?,和大家一起聊一聊?duì)象賦值的問(wèn)題。
在實(shí)際的項(xiàng)目開(kāi)發(fā)中,對(duì)象間賦值普遍存在,隨著雙十一、秒殺等電商過(guò)程愈加復(fù)雜,數(shù)據(jù)量也在不斷攀升,效率問(wèn)題,浮出水面。
問(wèn):如果是你來(lái)寫(xiě)對(duì)象間賦值的代碼,你會(huì)怎么做?
答:想都不用想,直接代碼走起來(lái),get、set即可。
問(wèn):下圖這樣?
答:對(duì)啊,你怎么能把我的代碼放到網(wǎng)上?
問(wèn):沒(méi),我只是舉個(gè)例子
答:這涉及到商業(yè)機(jī)密,是很?chē)?yán)重的問(wèn)題
問(wèn):我發(fā)現(xiàn)你挺能扯皮啊,直接回答問(wèn)題行嗎?
答:OK,OK,我也覺(jué)得這樣寫(xiě)很low,上次這么寫(xiě)之后,差點(diǎn)挨打
- 對(duì)象太多,ctrl c + strl v,鍵盤(pán)差點(diǎn)沒(méi)敲壞;
- 而且很容易出錯(cuò),一不留神,屬性沒(méi)對(duì)應(yīng)上,賦錯(cuò)值了;
- 代碼看起來(lái)很傻缺,一個(gè)類(lèi)好幾千行,全是get、set復(fù)制,還起個(gè)了自以為很優(yōu)雅的名字transfer;
- 如果屬性名不能見(jiàn)名知意,還得加上每個(gè)屬性的含義注釋?zhuān)ɑ具@種賦值操作,都是要加的,注釋很重要,注釋很重要,注釋很重要);
- 代碼維護(hù)起來(lái)很麻煩;
- 如果對(duì)象過(guò)多,會(huì)產(chǎn)生類(lèi)爆炸問(wèn)題,如果屬性過(guò)多,會(huì)嚴(yán)重違背阿里巴巴代碼規(guī)約(一個(gè)方法的實(shí)際代碼最多20行);
問(wèn):行了,行了,說(shuō)說(shuō),怎么解決吧。
答:很簡(jiǎn)單啊,可以通過(guò)工具類(lèi)Beanutils直接賦值啊
問(wèn):我聽(tīng)說(shuō)工具類(lèi)最近很卷,你用的哪個(gè)?。?/h4>
答:就Apache自帶的那個(gè)啊,賊簡(jiǎn)單。我手寫(xiě)一個(gè),給你欣賞一下。
問(wèn):你這代碼報(bào)錯(cuò)啊,避免用Apache Beanutils進(jìn)行屬性的copy。
答:沒(méi)報(bào)錯(cuò),只是嚴(yán)重警告而已,代碼能跑就行,有問(wèn)題再優(yōu)化唄
問(wèn):你這什么態(tài)度?人事在哪劃拉的人,為啥會(huì)出現(xiàn)嚴(yán)重警告?
答:拿多少錢(qián),干多少活,我又不是XXX,應(yīng)該是性能問(wèn)題吧
問(wèn):具體什么原因?qū)е碌哪兀?/p>
答:3000塊錢(qián)還得手撕一下 apache copyProperties 的源代碼唄?
通過(guò)單例模式調(diào)用copyProperties,但是,每一個(gè)方法對(duì)應(yīng)一個(gè)BeanUtilsBean.getInstance()實(shí)例,每一個(gè)類(lèi)實(shí)例對(duì)應(yīng)一個(gè)實(shí)例,這不算一個(gè)真正的單例模式。
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}性能瓶頸 --> 日志太多也是病
通過(guò)源碼可以看到,每一個(gè)copyProperties都要進(jìn)行多次類(lèi)型檢查,還要打印日志。
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
// 類(lèi)型檢查
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;
// 類(lèi)型檢查
// DanyBean 提供了可以動(dòng)態(tài)修改實(shí)現(xiàn)他的類(lèi)的屬性名稱(chēng)、屬性值、屬性類(lèi)型的功能
if (orig instanceof DynaBean) {
// 獲取源對(duì)象所有屬性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
DynaProperty origDescriptor = var4[var6];
// 獲取源對(duì)象屬性名
name = origDescriptor.getName();
// 判斷源對(duì)象是否可讀、判斷目標(biāo)對(duì)象是否可寫(xiě)
if (this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
// 獲取對(duì)應(yīng)的值
value = ((DynaBean)orig).get(name);
// 每個(gè)屬性都調(diào)用一次copyProperty
this.copyProperty(dest, name, value);
}
}
} else if (orig instanceof Map) {
...
} else {
...
}
}
}通過(guò) jvisualvm.exe 檢測(cè)代碼性能
再通過(guò)jvisualvm.exe檢測(cè)一下運(yùn)行情況,果然,logging.log4j赫然在列,穩(wěn)居耗時(shí)Top1。
問(wèn):還有其它好的方式嗎?性能好一點(diǎn)的
答:當(dāng)然有,據(jù)我了解有 4 種工具類(lèi),實(shí)際上,可能會(huì)有更多,話(huà)不多說(shuō),先簡(jiǎn)單介紹一下。
- org.apache.commons.beanutils.BeanUtils。
- org.apache.commons.beanutils.PropertyUtils。
- org.springframework.cglib.beans.BeanCopier。
- org.springframework.beans.BeanUtils。
問(wèn):那你怎么不用?
答:OK,我來(lái)演示一下
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)計(jì)
|
方法 |
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毫秒 |
不測(cè)不知道,一測(cè)嚇一跳,差的還真的多。
spring cglib BeanCopier性能最好,apache BeanUtils性能最差。
性能走勢(shì) --> spring cglib BeanCopier 優(yōu)于 spring copyProperties 優(yōu)于 apache PropertyUtils 優(yōu)于 apache BeanUtils
避免用Apache Beanutils進(jìn)行屬性的copy的問(wèn)題 上面分析完了,下面再看看其它的方法做了哪些優(yōu)化。
Apache PropertyUtils 源碼分析?
從源碼可以清晰的看到,類(lèi)型檢查變成了非空校驗(yàn),去掉了每一次copy的日志記錄,性能肯定更好了。
- 類(lèi)型檢查變成了非空校驗(yàn)
- 去掉了每一次copy的日志記錄
- 實(shí)際賦值的地方由copyProperty變成了DanyBean + setSimpleProperty;
DanyBean 提供了可以動(dòng)態(tài)修改實(shí)現(xiàn)他的類(lèi)的屬性名稱(chēng)、屬性值、屬性類(lèi)型的功能。
public void copyProperties(Object dest, Object orig){
// 判斷數(shù)據(jù)源和目標(biāo)對(duì)象不是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中最為耗時(shí)的log日志記錄
int var5;
int var6;
String name;
Object value;
// 類(lèi)型檢查
if (orig instanceof DynaBean) {
// 獲取源對(duì)象所有屬性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
DynaProperty origDescriptor = var4[var6];
// 獲取源對(duì)象屬性名
name = origDescriptor.getName();
// 判斷源對(duì)象是否可讀、判斷目標(biāo)對(duì)象是否可寫(xiě)
if (this.isReadable(orig, name) && this.isWriteable(dest, name)) {
// 獲取對(duì)應(yīng)的值
value = ((DynaBean)orig).get(name);
// 相對(duì)于org.apache.commons.beanutils.BeanUtils.copyProperties此處有優(yōu)化
// DanyBean 提供了可以動(dòng)態(tài)修改實(shí)現(xiàn)他的類(lèi)的屬性名稱(chēng)、屬性值、屬性類(lèi)型的功能
if (dest instanceof DynaBean) {
((DynaBean)dest).set(name, value);
} else {
// 每個(gè)屬性都調(diào)用一次copyProperty
this.setSimpleProperty(dest, name, value);
}
}
}
} else if (orig instanceof Map) {
...
} else {
...
}
}
}通過(guò) jvisualvm.exe 檢測(cè)代碼性能
再通過(guò)jvisualvm.exe檢測(cè)一下運(yùn)行情況,果然,logging.log4j沒(méi)有了,其他的基本不變。
Spring copyProperties 源碼分析?
- 判斷數(shù)據(jù)源和目標(biāo)對(duì)象的非空判斷改為了斷言。
- 每次copy沒(méi)有日志記錄。
- 沒(méi)有if (orig instanceof DynaBean) {這個(gè)類(lèi)型檢查。
- 增加了放開(kāi)權(quán)限的步驟。
private static void copyProperties(Object source, Object target, @Nullable Class> editable,
@Nullable String... ignoreProperties){
// 判斷數(shù)據(jù)源和目標(biāo)對(duì)象不是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);
// 需要忽略的屬性
ListignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
// 目標(biāo)對(duì)象存在寫(xiě)入方法、屬性不被忽略
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
/**
* 源對(duì)象存在讀取方法、數(shù)據(jù)是可復(fù)制的
* writeMethod.getParameterTypes()[0]:獲取 writeMethod 的第一個(gè)入?yún)㈩?lèi)型
* readMethod.getReturnType():獲取 readMethod 的返回值類(lèi)型
* 判斷返回值類(lèi)型和入?yún)㈩?lèi)型是否存在繼承關(guān)系,只有是繼承關(guān)系或相等的情況下,才會(huì)進(jìn)行注入
*/
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
// 放開(kāi)讀取方法的權(quán)限
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
// 通過(guò)反射獲取值
Object value = readMethod.invoke(source);
// 放開(kāi)寫(xiě)入方法的權(quán)限
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
// 通過(guò)反射寫(xiě)入值
writeMethod.invoke(target, value);
}
}
}
}
}
總結(jié)?
阿里的友情提示,避免用Apache Beanutils進(jìn)行對(duì)象的copy,還是很有道理的。
Apache Beanutils 的性能問(wèn)題出現(xiàn)在類(lèi)型校驗(yàn)和每一次copy的日志記錄。
Apache PropertyUtils 進(jìn)行了如下優(yōu)化:
- 類(lèi)型檢查變成了非空校驗(yàn)。
- 去掉了每一次copy的日志記錄。
- 實(shí)際賦值的地方由copyProperty變成了DanyBean + setSimpleProperty。
Spring copyProperties 進(jìn)行了如下優(yōu)化:
- 判斷數(shù)據(jù)源和目標(biāo)對(duì)象的非空判斷改為了斷言。
- 每次copy沒(méi)有日志記錄。
- 沒(méi)有if (orig instanceof DynaBean) {這個(gè)類(lèi)型檢查。
- 增加了放開(kāi)權(quán)限的步驟。
本文轉(zhuǎn)載自微信公眾號(hào)「哪吒編程」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系哪吒編程公眾號(hào)。
本文標(biāo)題:為什么要避免用ApacheBeanutils進(jìn)行屬性的copy
瀏覽路徑:http://m.5511xx.com/article/ccedjeh.html


咨詢(xún)
建站咨詢(xún)
