如何避免Apache?Beanutils屬性copy
引言
在實際的項目開發(fā)中,對象間賦值普遍存在,隨著雙十一、秒殺等電商過程愈加復雜,數(shù)據(jù)量也在不斷攀升,效率問題,浮出水面。
問:如果是你來寫對象間賦值的代碼,你會怎么做?
答:想都不用想,直接代碼走起來,get、set即可。
問:下圖這樣?

答:對啊,你怎么能把我的代碼放到網(wǎng)上?
問:沒,我只是舉個例子
答:這涉及到商業(yè)機密,是很嚴重的問題
問:我發(fā)現(xiàn)你挺能扯皮啊,直接回答問題行嗎?
答:OK,OK,我也覺得這樣寫很low,上次這么寫之后,差點挨打
- 對象太多,ctrl c + strl v,鍵盤差點沒敲壞;
- 而且很容易出錯,一不留神,屬性沒對應(yīng)上,賦錯值了;
- 代碼看起來很傻缺,一個類好幾千行,全是get、set復制,還起個了自以為很優(yōu)雅的名字transfer;
- 如果屬性名不能見名知意,還得加上每個屬性的含義注釋(基本這種賦值操作,都是要加的,注釋很重要,注釋很重要,注釋很重要);
- 代碼維護起來很麻煩;
- 如果對象過多,會產(chǎn)生類爆炸問題,如果屬性過多,會嚴重違背阿里巴巴代碼規(guī)約(一個方法的實際代碼最多20行);
問:行了,行了,說說,怎么解決吧。
答:很簡單啊,可以通過工具類Beanutils直接賦值啊
問:我聽說工具類最近很卷,你用的哪個?。?/h2>
答:就Apache自帶的那個啊,賊簡單。我手寫一個,給你欣賞一下。

問:你這代碼報錯啊,避免用Apache Beanutils進行屬性的copy。
答:沒報錯,只是嚴重警告而已,代碼能跑就行,有問題再優(yōu)化唄
問:為啥會出現(xiàn)嚴重警告?
答:拿多少錢,干多少活,我又不是XXX,應(yīng)該是性能問題吧
問:具體什么原因?qū)е碌哪兀?/p>
答:3000塊錢還得手撕一下 apache copyProperties 的源代碼唄?
通過單例模式調(diào)用copyProperties,但是,每一個方法對應(yīng)一個BeanUtilsBean.getInstance()實例,每一個類實例對應(yīng)一個實例,這不算一個真正的單例模式。
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
性能瓶頸 --> 日志太多也是病
通過源碼可以看到,每一個copyProperties都要進行多次類型檢查,還要打印日志。
/**
* org.apache.commons.beanutils.BeanUtils.copyProperties方法源碼解析
* @author 哪吒編程
* @time 2023-01-07
*/
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)修改實現(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();
// 判斷源對象是否可讀、判斷目標對象是否可寫
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) {
Map<String, Object> propMap = (Map)orig;
Iterator var13 = propMap.entrySet().iterator();
while(var13.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry)var13.next();
String name = (String)entry.getKey();
if (this.getPropertyUtils().isWriteable(dest, name)) {
this.copyProperty(dest, name, entry.getValue());
}
}
} else {
PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);
PropertyDescriptor[] var14 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
PropertyDescriptor origDescriptor = var14[var6];
name = origDescriptor.getName();
if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
try {
value = this.getPropertyUtils().getSimpleProperty(orig, name);
this.copyProperty(dest, name, value);
} catch (NoSuchMethodException var10) {
}
}
}
}
}
}
通過 jvisualvm.exe 檢測代碼性能
再通過jvisualvm.exe檢測一下運行情況,果然,logging.log4j赫然在列,穩(wěn)居耗時Top1。

問:還有其它好的方式嗎?性能好一點的
答:當然有,據(jù)我了解有 4 種工具類,實際上,可能會有更多,話不多說,先簡單介紹一下。
org.apache.commons.beanutils.BeanUtils;
org.apache.commons.beanutils.PropertyUtils;
org.springframework.cglib.beans.BeanCopier;
org.springframework.beans.BeanUtils;
問:那你怎么不用?
答:OK,我來演示一下
package com.nezha.copy;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.StopWatch;
public class Test {
public static void main(String[] args) {
User user = new User();
user.setUserId("1");
user.setUserName("哪吒編程");
user.setCardId("123");
user.setCreateTime("2023-01-03");
user.setEmail("666666666@qq.com");
user.setOperate("哪吒");
user.setOrgId("46987916");
user.setPassword("123456");
user.setPhone("10086");
user.setRemark("456");
user.setSex(1);
user.setStatus("1");
user.setTel("110");
user.setType("0");
user.setUpdateTime("2023-01-05");
User target = new User();
int sum = 10000000;
apacheBeanUtilsCopyTest(user,target,sum);
commonsPropertyCopyTest(user,target,sum);
cglibBeanCopyTest(user,target,sum);
springBeanCopyTest(user,target,sum);
}
private static void apacheBeanUtilsCopyTest(User source, User target, int sum) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++) {
apacheBeanUtilsCopy(source,target);
}
stopWatch.stop();
System.out.println("使用org.apache.commons.beanutils.BeanUtils方式賦值"+sum+"個user對象,耗時:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
}
/**
* org.apache.commons.beanutils.BeanUtils方式
*/
private static void apacheBeanUtilsCopy(User source, User target) {
try {
BeanUtils.copyProperties(source, target);
} catch (Exception e) {
}
}
private static void commonsPropertyCopyTest(User source, User target, int sum) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++) {
commonsPropertyCopy(source,target);
}
stopWatch.stop();
System.out.println("使用org.apache.commons.beanutils.PropertyUtils方式賦值"+sum+"個user對象,耗時:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
}
/**
* org.apache.commons.beanutils.PropertyUtils方式
*/
private static void commonsPropertyCopy(User source, User target) {
try {
PropertyUtils.copyProperties(target, source);
} catch (Exception e) {
}
}
private static void cglibBeanCopyTest(User source, User target, int sum) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++) {
cglibBeanCopy(source,target);
}
stopWatch.stop();
System.out.println("使用org.springframework.cglib.beans.BeanCopier方式賦值"+sum+"個user對象,耗時:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
}
/**
* org.springframework.cglib.beans.BeanCopier方式
*/
static BeanCopier copier = BeanCopier.create(User.class, User.class, false);
private static void cglibBeanCopy(User source, User target) {
copier.copy(source, target, null);
}
private static void springBeanCopyTest(User source, User target, int sum) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++) {
springBeanCopy(source,target);
}
stopWatch.stop();
System.out.println("使用org.springframework.beans.BeanUtils.copyProperties方式賦值"+sum+"個user對象,耗時:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
}
/**
* org.springframework.beans.BeanUtils.copyProperties方式
*/
private static void springBeanCopy(User source, User target) {
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進行屬性的copy的問題 上面分析完了,下面再看看其它的方法做了哪些優(yōu)化。
Apache PropertyUtils 源碼分析
從源碼可以清晰的看到,類型檢查變成了非空校驗,去掉了每一次copy的日志記錄,性能肯定更好了。
- 類型檢查變成了非空校驗
- 去掉了每一次copy的日志記錄
- 實際賦值的地方由copyProperty變成了DanyBean + setSimpleProperty;
DanyBean 提供了可以動態(tài)修改實現(xiàn)他的類的屬性名稱、屬性值、屬性類型的功能。
/**
* org.apache.commons.beanutils.PropertyUtils方式源碼解析
* @author 哪吒編程
* @time 2023-01-07
*/
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
// 判斷數(shù)據(jù)源和目標對象不是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();
// 判斷源對象是否可讀、判斷目標對象是否可寫
if (this.isReadable(orig, name) && this.isWriteable(dest, name)) {
try {
// 獲取對應(yīng)的值
value = ((DynaBean)orig).get(name);
// 相對于org.apache.commons.beanutils.BeanUtils.copyProperties此處有優(yōu)化
// DanyBean 提供了可以動態(tài)修改實現(xiàn)他的類的屬性名稱、屬性值、屬性類型的功能
if (dest instanceof DynaBean) {
((DynaBean)dest).set(name, value);
} else {
// 每個屬性都調(diào)用一次copyProperty
this.setSimpleProperty(dest, name, value);
}
} catch (NoSuchMethodException var12) {
if (this.log.isDebugEnabled()) {
this.log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", var12);
}
}
}
}
} else if (orig instanceof Map) {
Iterator entries = ((Map)orig).entrySet().iterator();
while(true) {
Map.Entry entry;
String name;
do {
if (!entries.hasNext()) {
return;
}
entry = (Map.Entry)entries.next();
name = (String)entry.getKey();
} while(!this.isWriteable(dest, name));
try {
if (dest instanceof DynaBean) {
((DynaBean)dest).set(name, entry.getValue());
} else {
this.setSimpleProperty(dest, name, entry.getValue());
}
} catch (NoSuchMethodException var11) {
if (this.log.isDebugEnabled()) {
this.log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", var11);
}
}
}
} else {
PropertyDescriptor[] origDescriptors = this.getPropertyDescriptors(orig);
PropertyDescriptor[] var16 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
PropertyDescriptor origDescriptor = var16[var6];
name = origDescriptor.getName();
if (this.isReadable(orig, name) && this.isWriteable(dest, name)) {
try {
value = this.getSimpleProperty(orig, name);
if (dest instanceof DynaBean) {
((DynaBean)dest).set(name, value);
} else {
this.setSimpleProperty(dest, name, value);
}
} catch (NoSuchMethodException var10) {
if (this.log.isDebugEnabled()) {
this.log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", var10);
}
}
}
}
}
}
}
通過 jvisualvm.exe 檢測代碼性能
再通過jvisualvm.exe檢測一下運行情況,果然,logging.log4j沒有了,其他的基本不變。

Spring copyProperties 源碼分析
- 判斷數(shù)據(jù)源和目標對象的非空判斷改為了斷言;
- 每次copy沒有日志記錄;
- 沒有
if (orig instanceof DynaBean) {這個類型檢查; - 增加了放開權(quán)限的步驟;
/**
* org.springframework.beans.BeanUtils.copyProperties方法源碼解析
* @author 哪吒編程
* @time 2023-01-07
*/
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
// 判斷數(shù)據(jù)源和目標對象不是null
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
/**
* 若target設(shè)置了泛型,則默認使用泛型
* 若是 editable 是 null,則此處忽略
* 一般情況下editable都默認為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();
// 目標對象存在寫入方法、屬性不被忽略
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ù)是可復制的
* writeMethod.getParameterTypes()[0]:獲取 writeMethod 的第一個入?yún)㈩愋?
* readMethod.getReturnType():獲取 readMethod 的返回值類型
* 判斷返回值類型和入?yún)㈩愋褪欠翊嬖诶^承關(guān)系,只有是繼承關(guān)系或相等的情況下,才會進行注入
*/
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
// 放開讀取方法的權(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);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
總結(jié)
阿里的友情提示,避免用Apache Beanutils進行對象的copy,還是很有道理的。
Apache Beanutils 的性能問題出現(xiàn)在類型校驗和每一次copy的日志記錄;
Apache PropertyUtils 進行了如下優(yōu)化:
- 類型檢查變成了非空校驗
- 去掉了每一次copy的日志記錄
- 實際賦值的地方由copyProperty變成了DanyBean + setSimpleProperty;
Spring copyProperties 進行了如下優(yōu)化:
- 判斷數(shù)據(jù)源和目標對象的非空判斷改為了斷言;
- 每次copy沒有日志記錄;
- 沒有
if (orig instanceof DynaBean) {這個類型檢查; - 增加了放開權(quán)限的步驟;
以上就是如何避免Apache Beanutils屬性copy的詳細內(nèi)容,更多關(guān)于Apache Beanutils屬性copy的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot開發(fā)項目,引入JPA找不到findOne方法的解決
這篇文章主要介紹了SpringBoot開發(fā)項目,引入JPA找不到findOne方法的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Java選擇結(jié)構(gòu)與循環(huán)結(jié)構(gòu)的使用詳解
循環(huán)結(jié)構(gòu)是指在程序中需要反復執(zhí)行某個功能而設(shè)置的一種程序結(jié)構(gòu)。它由循環(huán)體中的條件,判斷繼續(xù)執(zhí)行某個功能還是退出循環(huán),選擇結(jié)構(gòu)用于判斷給定的條件,根據(jù)判斷的結(jié)果判斷某些條件,根據(jù)判斷的結(jié)果來控制程序的流程2022-03-03
IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文(必看)
這篇文章主要介紹了IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文了,本文通過截圖的形式給大家展示,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04
Java中的StringTokenizer實現(xiàn)字符串切割詳解
這篇文章主要介紹了Java中的StringTokenizer實現(xiàn)字符串切割詳解,java.util工具包提供了字符串切割的工具類StringTokenizer,Spring等常見框架的字符串工具類(如Spring的StringUtils),需要的朋友可以參考下2024-01-01

