成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

為什么阿里代碼規(guī)約要求避免使用 Apache BeanUtils 進行屬性的拷貝

raledong / 936人閱讀

摘要:于是我建議這位小伙伴使用了進行屬性拷貝,這為我們的程序挖了一個坑阿里代碼規(guī)約當我們開啟阿里代碼掃描插件時,如果你使用了進行屬性拷貝,它會給你一個非常嚴重的警告。大名鼎鼎的提供的包,居然會存在性能問題,以致于阿里給出了嚴重的警告。

聲明:本文屬原創(chuàng)文章,始發(fā)于公號:程序員自學之道,并同步發(fā)布于 https://blog.csdn.net/dadiyang,特此,同步發(fā)布到 sf,轉(zhuǎn)載請注明出處。

緣起

有一次開發(fā)過程中,剛好看到一個小伙伴在調(diào)用 set 方法,將一個數(shù)據(jù)庫中查詢出來的 PO 對象的屬性拷貝到 Vo 對象中,類似這樣:

可以看出,Po 和 Vo 兩個類的字段絕大部分是一樣的,我們一個個地調(diào)用 set 方法只是做了一些重復的冗長的操作。這種操作非常容易出錯,因為對象的屬性太多,有可能會漏掉一兩個,而且肉眼很難察覺。

類似這樣的操作,我們可以很容易想到,可以通過反射來解決。其實,如此普遍通用的功能,一個 BeanUtils 工具類就可以搞定了。

于是我建議這位小伙伴使用了 Apache BeanUtils.copyProperties 進行屬性拷貝,這為我們的程序挖了一個坑!

阿里代碼規(guī)約

當我們開啟阿里代碼掃描插件時,如果你使用了 Apache BeanUtils.copyProperties 進行屬性拷貝,它會給你一個非常嚴重的警告。因為,Apache BeanUtils性能較差,可以使用 Spring BeanUtils 或者 Cglib BeanCopier 來代替

看到這樣的警告,有點讓人有點不爽。大名鼎鼎的 Apache 提供的包,居然會存在性能問題,以致于阿里給出了嚴重的警告。

那么,這個性能問題究竟是有多嚴重呢?畢竟,在我們的應(yīng)用場景中,如果只是很微小的性能損耗,但是能帶來非常大的便利性,還是可以接受的。

帶著這個問題。我們來做一個實驗,驗證一下。

如果對具體的測試方式?jīng)]有興趣,可以跳過直接看結(jié)果哦~

測試方法接口和實現(xiàn)定義

首先,為了測試方便,讓我們來定義一個接口,并將幾種實現(xiàn)統(tǒng)一起來:

public interface PropertiesCopier {
    void copyProperties(Object source, Object target) throws Exception;
}
public class CglibBeanCopierPropertiesCopier implements PropertiesCopier {
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        BeanCopier copier = BeanCopier.create(source.getClass(), target.getClass(), false);
        copier.copy(source, target, null);
    }
}
// 全局靜態(tài) BeanCopier,避免每次都生成新的對象
public class StaticCglibBeanCopierPropertiesCopier implements PropertiesCopier {
    private static BeanCopier copier = BeanCopier.create(Account.class, Account.class, false);
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        copier.copy(source, target, null);
    }
}
public class SpringBeanUtilsPropertiesCopier implements PropertiesCopier {
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        org.springframework.beans.BeanUtils.copyProperties(source, target);
    }
}
public class CommonsBeanUtilsPropertiesCopier implements PropertiesCopier {
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        org.apache.commons.beanutils.BeanUtils.copyProperties(target, source);
    }
}
public class CommonsPropertyUtilsPropertiesCopier implements PropertiesCopier {
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        org.apache.commons.beanutils.PropertyUtils.copyProperties(target, source);
    }
}
單元測試

然后寫一個參數(shù)化的單元測試:

@RunWith(Parameterized.class)
public class PropertiesCopierTest {
    @Parameterized.Parameter(0)
    public PropertiesCopier propertiesCopier;
    // 測試次數(shù)
    private static List testTimes = Arrays.asList(100, 1000, 10_000, 100_000, 1_000_000);
    // 測試結(jié)果以 markdown 表格的形式輸出
    private static StringBuilder resultBuilder = new StringBuilder("|實現(xiàn)|100|1,000|10,000|100,000|1,000,000|
").append("|----|----|----|----|----|----|
");

    @Parameterized.Parameters
    public static Collection data() {
        Collection params = new ArrayList<>();
        params.add(new Object[]{new StaticCglibBeanCopierPropertiesCopier()});
        params.add(new Object[]{new CglibBeanCopierPropertiesCopier()});
        params.add(new Object[]{new SpringBeanUtilsPropertiesCopier()});
        params.add(new Object[]{new CommonsPropertyUtilsPropertiesCopier()});
        params.add(new Object[]{new CommonsBeanUtilsPropertiesCopier()});
        return params;
    }

    @Before
    public void setUp() throws Exception {
        String name = propertiesCopier.getClass().getSimpleName().replace("PropertiesCopier", "");
        resultBuilder.append("|").append(name).append("|");
    }

    @Test
    public void copyProperties() throws Exception {
        Account source = new Account(1, "test1", 30D);
        Account target = new Account();
        // 預熱一次
        propertiesCopier.copyProperties(source, target);
        for (Integer time : testTimes) {
            long start = System.nanoTime();
            for (int i = 0; i < time; i++) {
                propertiesCopier.copyProperties(source, target);
            }
            resultBuilder.append((System.nanoTime() - start) / 1_000_000D).append("|");
        }
        resultBuilder.append("
");
    }

    @AfterClass
    public static void tearDown() throws Exception {
        System.out.println("測試結(jié)果:");
        System.out.println(resultBuilder);
    }
}
測試結(jié)果

時間單位毫秒

實現(xiàn) 100次 1,000次 10,000次 100,000次 1,000,000次
StaticCglibBeanCopier 0.055022 0.541029 0.999478 2.754824 9.88556
CglibBeanCopier 5.320798 11.086323 61.037446 72.484607 333.384007
SpringBeanUtils 5.180483 21.328542 30.021662 103.266375 966.439272
CommonsPropertyUtils 9.729159 42.927356 74.063789 386.127787 1955.5437
CommonsBeanUtils 24.99513 170.728558 572.335327 2970.3068 27563.3459

結(jié)果表明,Cglib 的 BeanCopier 的拷貝速度是最快的,即使是百萬次的拷貝也只需要 10 毫秒!
相比而言,最差的是 Commons 包的 BeanUtils.copyProperties 方法,100 次拷貝測試與表現(xiàn)最好的 Cglib 相差 400 倍之多。百萬次拷貝更是出現(xiàn)了 2800 倍的性能差異!

結(jié)果真是讓人大跌眼鏡。

但是它們?yōu)槭裁磿羞@么大的差異呢?

原因分析

查看源碼,我們會發(fā)現(xiàn) CommonsBeanUtils 主要有以下幾個耗時的地方:

輸出了大量的日志調(diào)試信息

重復的對象類型檢查

類型轉(zhuǎn)換

  public void copyProperties(final Object dest, final Object orig)
        throws IllegalAccessException, InvocationTargetException {
        // 類型檢查 
        if (orig instanceof DynaBean) {
            ...
        } else if (orig instanceof Map) {
           ...
        } else {
            final PropertyDescriptor[] origDescriptors = ...
            for (PropertyDescriptor origDescriptor : origDescriptors) {
                ...
                // 這里每個屬性都調(diào)一次 copyProperty
                copyProperty(dest, name, value);
            }
        }
    }

    public void copyProperty(final Object bean, String name, Object value)
        throws IllegalAccessException, InvocationTargetException {
        ...
        // 這里又進行一次類型檢查
        if (target instanceof DynaBean) {
            ...
        }
        ...
        // 需要將屬性轉(zhuǎn)換為目標類型
        value = convertForCopy(value, type);
        ...
    }
    // 而這個 convert 方法在日志級別為 debug 的時候有很多的字符串拼接
    public  T convert(final Class type, Object value) {
        if (log().isDebugEnabled()) {
            log().debug("Converting" + (value == null ? "" : " "" + toString(sourceType) + """) + " value "" + value + "" to type "" + toString(targetType) + """);
        }
        ...
        if (targetType.equals(String.class)) {
            return targetType.cast(convertToString(value));
        } else if (targetType.equals(sourceType)) {
            if (log().isDebugEnabled()) {
                log().debug("No conversion required, value is already a " + toString(targetType));
            }
            return targetType.cast(value);
        } else {
            // 這個 convertToType 方法里也需要做類型檢查
            final Object result = convertToType(targetType, value);
            if (log().isDebugEnabled()) {
                log().debug("Converted to " + toString(targetType) + " value "" + result + """);
            }
            return targetType.cast(result);
        }
    }

具體的性能和源碼分析,可以參考這幾篇文章:

幾種copyProperties工具類性能比較:https://www.jianshu.com/p/bcb...

CGLIB中BeanCopier源碼實現(xiàn):https://www.jianshu.com/p/f8b...

Java Bean Copy框架性能對比:https://yq.aliyun.com/article...

One more thing

除了性能問題之外,在使用 CommonsBeanUtils 時還有其他的坑需要特別小心!

包裝類默認值

在進行屬性拷貝時,雖然 CommonsBeanUtils 默認不會給原始包裝類賦默認值的,但是在使用低版本(1.8.0及以下)的時候,如果你的類有 Date 類型屬性,而且來源對象中該屬性值為 null 的話,就會發(fā)生異常:

org.apache.commons.beanutils.ConversionException: No value specified for "Date"

解決這個問題的辦法是注冊一個 DateConverter:

ConvertUtils.register(new DateConverter(null), java.util.Date.class);

然而這個語句,會導致包裝類型會被賦予原始類型的默認值,如 Integer 屬性默認賦值為 0,盡管你的來源對象該字段的值為 null。

在高版本(1.9.3)中,日期 null 值的問題和包裝類賦默認值的問題都被修復了。

這個在我們的包裝類屬性為 null 值時有特殊含義的場景,非常容易踩坑!例如搜索條件對象,一般 null 值表示該字段不做限制,而 0 表示該字段的值必須為0。

改用其他工具時

當我們看到阿里的提示,或者你看了這篇文章之后,知道了 CommonsBeanUtils 的性能問題,想要改用 Spring 的 BeanUtils 時,要小心:

org.apache.commons.beanutils.BeanUtils.copyProperties(Object target, Object source);
org.springframework.beans.BeanUtils.copyProperties(Object source, Object target);

從方法簽名上可以看出,這兩個工具類的名稱相同,方法名也相同,甚至連參數(shù)個數(shù)、類型、名稱都相同。但是參數(shù)的位置是相反的。因此,如果你想更改的時候,千萬要記得,將 target 和 source 兩個參數(shù)也調(diào)換過來!

另外,可能由于種種原因,你獲取的堆棧信息不完整找不到問題在哪,所以這里順便提醒一下:

如果你遇到 java.lang.IllegalArgumentException: Source must not be null 或者 java.lang.IllegalArgumentException: Target must not be null 這樣的異常信息卻到處找不到原因時,不用找了,這是由于你在 copyProperties 的時候傳了 null 值導致的。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74742.html

相關(guān)文章

  • JAVA代碼規(guī)范

    摘要:前言作為一名全干打字員,干活時經(jīng)常會被要求使用各種各樣的語言去實現(xiàn)各種各樣的需求,來回切換起來寫的代碼就會或多或少有點不規(guī)范。今天我們以為例,講講在代碼中,我們需要注意的某些規(guī)范。 前言 作為一名全干打字員,干活時經(jīng)常會被要求使用各種各樣的語言去實現(xiàn)各種各樣的需求,來回切換起來寫的代碼就會或多或少有點不規(guī)范。今天我們以JAVA為例,講講在代碼中,我們需要注意的某些規(guī)范。(本文標準依賴于...

    GHOST_349178 評論0 收藏0
  • BeanUtils工具使用細節(jié)

    摘要:拷貝操作又一個非常好用的工具類和中分別存在一個,提供了對。除了支持基本類型以及基本類型的數(shù)組之外,還支持這些類的對象,其余一概不支持。而且,由于這些類都是采用反射機制實現(xiàn)的,對程序的效率也會有影響。因此,慎用或者使用看效果如何 java bean拷貝操作又一個非常好用的工具類 BeanUitls :spring (org.springframework.beans.BeanUtils)...

    afishhhhh 評論0 收藏0
  • 常識之外規(guī)范——阿里java開發(fā)手冊筆記(全章節(jié))

    摘要:說明這篇文章是我第一次認真閱讀阿里巴巴開發(fā)手冊終極版的筆記。說明本手冊明確防止是調(diào)用者的責任。一年半載后,那么單元測試幾乎處于廢棄狀態(tài)。好的單元測試能夠最大限度地規(guī)避線上故障。 說明 這篇文章是我第一次(認真)閱讀《阿里巴巴 Java 開發(fā)手冊(終極版)》的筆記。手冊本身對規(guī)范的講解已經(jīng)非常詳細了,如果你已經(jīng)有一定的開發(fā)經(jīng)驗并且有良好的編碼習慣和意識,會發(fā)現(xiàn)大部分規(guī)范是符合常識的。所以...

    Martin91 評論0 收藏0
  • 新增16條設(shè)計規(guī)約!阿里巴巴Java開發(fā)手冊(詳盡版)開放下載!

    摘要:熟悉和遵守阿里巴巴開發(fā)手冊的編程風格,那只是標,而代碼可讀性的本可以追溯到軟件設(shè)計階段。何為條設(shè)計規(guī)約是根據(jù)阿里巴巴實際項目架構(gòu)經(jīng)驗提煉而成,共條。本次新增的不單是條新的設(shè)計規(guī)約,還是千萬阿里人的技術(shù)之心。 摘要:2018年6月,《阿里巴巴Java開發(fā)手冊》再次刷新代碼規(guī)范認知,我們新增了16條設(shè)計規(guī)約!現(xiàn)免費開放下載,不可錯過!《阿里巴巴Java開發(fā)手冊》是阿里內(nèi)部Java工程師所遵...

    _ang 評論0 收藏0
  • 排名前16Java工具類

    摘要:在中,工具類定義了一組公共方法,這篇文章將介紹中使用最頻繁及最通用的工具類。另外,工具類,根據(jù)阿里開發(fā)手冊,包名如果要使用不能帶,工具類命名為 在Java中,工具類定義了一組公共方法,這篇文章將介紹Java中使用最頻繁及最通用的Java工具類。以下工具類、方法按使用流行度排名,參考數(shù)據(jù)來源于Github上隨機選取的5萬個開源項目源碼。 一. org.apache.commons.io....

    android_c 評論0 收藏0

發(fā)表評論

0條評論

raledong

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<