
在實際的項目開發(fā)中,對象間賦值普遍存在,隨著雙十一、秒殺等電商過程愈加復(fù)雜,數(shù)據(jù)量也在不斷攀升,效率問題,浮出水面。
答:想都不用想,直接代碼走起來,get、set即可。
問:下圖這樣?
【資料圖】
答:對啊,你怎么能把我的代碼放到網(wǎng)上?
問:沒,我只是舉個例子
答:這涉及到商業(yè)機密,是很嚴(yán)重的問題
問:我發(fā)現(xiàn)你挺能扯皮啊,直接回答問題行嗎?
問:行了,行了,說說,怎么解決吧。
答:很簡單啊,可以通過工具類Beanutils直接賦值啊
答:就??Apache?
?自帶的那個啊,賊簡單。我手寫一個,給你欣賞一下。
問:你這代碼報錯啊,避免用Apache Beanutils進行屬性的copy。
答:沒報錯,只是嚴(yán)重警告而已,??代碼能跑就行,有問題再優(yōu)化唄?
?
答:拿多少錢,干多少活,我又不是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(); // 判斷源對象是否可讀、判斷目標(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) { MappropMap = (Map)orig; Iterator var13 = propMap.entrySet().iterator(); while(var13.hasNext()) { Map.Entry 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?
?檢測一下運行情況,果然,??logging.log4j?
?赫然在列,穩(wěn)居耗時Top1。
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); }}
方法 | 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)化。
從源碼可以清晰的看到,類型檢查變成了非空校驗,去掉了每一次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ù)源和目標(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)) { 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檢測一下運行情況,果然,??logging.log4j?
?沒有了,其他的基本不變。
?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ù)源和目標(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); // 需要忽略的屬性 ListignoreList = (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)㈩愋? * 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); } } } } }}
阿里的友情提示,避免用??Apache Beanutils?
?進行對象的??copy?
?,還是很有道理的。
??Apache Beanutils?
? 的性能問題出現(xiàn)在類型校驗和每一次copy的日志記錄;
?if (orig instanceof DynaBean) {?
?這個類型檢查;增加了放開權(quán)限的步驟; 標(biāo)簽: 目標(biāo)對象 單例模式 是否可寫