溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

常見Bean拷貝框架的性能有哪些區(qū)別

發(fā)布時間:2021-10-13 09:12:58 來源:億速云 閱讀:97 作者:iii 欄目:編程語言

本篇內(nèi)容介紹了“常見Bean拷貝框架的性能有哪些區(qū)別”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

I.背景

當(dāng)業(yè)務(wù)量不大時,不管選擇哪個框架都沒什么問題,只要功能支持就ok了;但是當(dāng)數(shù)據(jù)量大的時候,可能就需要考慮性能問題了;再實際的項目中,正好遇到了這個問題,不僅慢,還發(fā)現(xiàn)會有鎖競爭,這特么就尼普了

項目中使用的是Spring的 BeanUtils, 版本 3.2.4.RELEASE, 版本相對較老,主要問題在于org.springframework.beans.CachedIntrospectionResults.forClass

/**
 * Create CachedIntrospectionResults for the given bean class.
 * <P>We don't want to use synchronization here. Object references are atomic,
 * so we can live with doing the occasional unnecessary lookup at startup only.
 * @param beanClass the bean class to analyze
 * @return the corresponding CachedIntrospectionResults
 * @throws BeansException in case of introspection failure
 */
static CachedIntrospectionResults forClass(Class beanClass) throws BeansException {
    CachedIntrospectionResults results;
    Object value;
    synchronized (classCache) {
        value = classCache.get(beanClass);
    }
    if (value instanceof Reference) {
        Reference ref = (Reference) value;
        results = (CachedIntrospectionResults) ref.get();
    }
    else {
        results = (CachedIntrospectionResults) value;
    }
    if (results == null) {
        if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
                isClassLoaderAccepted(beanClass.getClassLoader())) {
            results = new CachedIntrospectionResults(beanClass);
            synchronized (classCache) {
                classCache.put(beanClass, results);
            }
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
            }
            results = new CachedIntrospectionResults(beanClass);
            synchronized (classCache) {
                classCache.put(beanClass, new WeakReference<CachedIntrospectionResults>(results));
            }
        }
    }
    return results;
}

看上面的實現(xiàn),每次獲取value都加了一個同步鎖,而且還是鎖的全局的classCache,這就有些過分了啊,微妙的是這段代碼注釋,谷歌翻譯之后為

我們不想在這里使用同步。 對象引用是原子的,因此我們可以只在啟動時進行偶爾的不必要查找。

這意思大概是說我就在啟動的時候用一下,并不會頻繁的使用,所以使用了同步代碼塊也問題不大...

但是在BeanUtils#copyProperties中就蛋疼了,每次都會執(zhí)行這個方法,扎心了


當(dāng)然我們現(xiàn)在一般用的Spring5+了,這段代碼也早就做了改造了,新版的如下,不再存在上面的這個并發(fā)問題了

/**
 * Create CachedIntrospectionResults for the given bean class.
 * @param beanClass the bean class to analyze
 * @return the corresponding CachedIntrospectionResults
 * @throws BeansException in case of introspection failure
 */
@SuppressWarnings("unchecked")
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
    CachedIntrospectionResults results = strongClassCache.get(beanClass);
    if (results != null) {
        return results;
    }
    results = softClassCache.get(beanClass);
    if (results != null) {
        return results;
    }

    results = new CachedIntrospectionResults(beanClass);
    ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;

    if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
            isClassLoaderAccepted(beanClass.getClassLoader())) {
        classCacheToUse = strongClassCache;
    }
    else {
        if (logger.isDebugEnabled()) {
            logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
        }
        classCacheToUse = softClassCache;
    }

    CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
    return (existing != null ? existing : results);
}

II. 不同框架使用姿勢

接下來我們看一下幾種常見的bean拷貝框架的使用姿勢,以及對比測試

1. apache BeanUtils

阿里規(guī)范中,明確說明了,不要使用它,idea安裝阿里的代碼規(guī)范插件之后,會有提示

使用姿勢比較簡單,引入依賴

<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

屬性拷貝

@Component
public class ApacheCopier {
    public <K, T> T copy(K source, Class<T> target) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        T res = target.newInstance();
        // 注意,第一個參數(shù)為target,第二個參數(shù)為source
        // 與其他的正好相反 
        BeanUtils.copyProperties(res, source);
        return res;
    }
}

2. cglib BeanCopier

cglib是通過動態(tài)代理的方式來實現(xiàn)屬性拷貝的,與上面基于反射實現(xiàn)方式存在本質(zhì)上的區(qū)別,這也是它性能更優(yōu)秀的主因

在Spring環(huán)境下,一般不需要額外的引入依賴;或者直接引入spring-core

<!--      cglib  -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.8.RELEASE</version>
    <scope>compile</scope>
</dependency>

屬性拷貝

@Component
public class SpringCglibCopier {
    /**
     * cglib 對象轉(zhuǎn)換
     *
     * @param source
     * @param target
     * @param <K>
     * @param <T>
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public <K, T> T copy(K source, Class<T> target) throws IllegalAccessException, InstantiationException {
        BeanCopier copier = BeanCopier.create(source.getClass(), target, false);
        T res = target.newInstance();
        copier.copy(source, res, null);
        return res;
    }
}

當(dāng)然也可以直接使用純凈版的cglib,引入依賴

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

使用姿勢和上面一模一樣

@Component
public class PureCglibCopier {
    /**
     * cglib 對象轉(zhuǎn)換
     *
     * @param source
     * @param target
     * @param <K>
     * @param <T>
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public <K, T> T copy(K source, Class<T> target) throws IllegalAccessException, InstantiationException {
        BeanCopier copier = BeanCopier.create(source.getClass(), target, false);
        T res = target.newInstance();
        copier.copy(source, res, null);
        return res;
    }
}

3. spring BeanUtils

這里使用的是spring 5.2.1.RELEASE, 就不要拿3.2來使用了,不然并發(fā)下的性能實在是感人

基于內(nèi)省+反射,借助getter/setter方法實現(xiàn)屬性拷貝,性能比apache高

核心依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.1.RELEASE</version>
    <scope>compile</scope>
</dependency>

屬性拷貝

@Component
public class SpringBeanCopier {

    /**
     * 對象轉(zhuǎn)換
     *
     * @param source
     * @param target
     * @param <K>
     * @param <T>
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public <K, T> T copy(K source, Class<T> target) throws IllegalAccessException, InstantiationException {
        T res = target.newInstance();
        BeanUtils.copyProperties(source, res);
        return res;
    }
}

4. hutool BeanUtil

hutool 提供了很多的java工具類,從測試效果來看它的性能比apache會高一點,當(dāng)?shù)陀趕pring

引入依賴

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-core</artifactId>
    <version>5.6.0</version>
</dependency>

使用姿勢

@Component
public class HutoolCopier {

    /**
     * bean 對象轉(zhuǎn)換
     *
     * @param source
     * @param target
     * @param <K>
     * @param <T>
     * @return
     */
    public <K, T> T copy(K source, Class<T> target) throws Exception {
        return BeanUtil.toBean(source, target);
    }
}

5. MapStruct

MapStruct 性能更強悍了,缺點也比較明顯,需要聲明bean的轉(zhuǎn)換接口,自動代碼生成的方式來實現(xiàn)拷貝,性能媲美直接的get/set

引入依賴

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.4.2.Final</version>
</dependency>

使用姿勢

@Mapper
public interface MapStructCopier {
    Target copy(Source source);
}

@Component
public class MapsCopier {
    private MapStructCopier mapStructCopier = Mappers.getMapper(MapStructCopier.class);

    public Target copy(Source source, Class<Target> target) {
        return mapStructCopier.copy(source);
    }
}

缺點也比較明顯,需要顯示的接口轉(zhuǎn)換聲明

6. 測試

定義兩個Bean,用于轉(zhuǎn)換測試,兩個bean的成員屬性名,類型完全一致

@Data
public class Source {
    private Integer id;
    private String user_name;
    private Double price;
    private List<Long> ids;
    private BigDecimal marketPrice;
}

@Data
public class Target {
    private Integer id;
    private String user_name;
    private Double price;
    private List<Long> ids;
    private BigDecimal marketPrice;
}
6.1 功能測試
private Random random = new Random();

public Source genSource() {
    Source source = new Source();
    source.setId(random.nextInt());
    source.setIds(Arrays.asList(random.nextLong(), random.nextLong(), random.nextLong()));
    source.setMarketPrice(new BigDecimal(random.nextFloat()));
    source.setPrice(random.nextInt(120) / 10.0d);
    source.setUser_name("一灰灰Blog");
    return source;
}


 private void copyTest() throws Exception {
        Source s = genSource();
        Target ta = apacheCopier.copy(s, Target.class);
        Target ts = springBeanCopier.copy(s, Target.class);
        Target tc = springCglibCopier.copy(s, Target.class);
        Target tp = pureCglibCopier.copy(s, Target.class);
        Target th = hutoolCopier.copy(s, Target.class);
        Target tm = mapsCopier.copy(s, Target.class);
        System.out.println("source:\t" + s + "\napache:\t" + ta + "\nspring:\t" + ts
                + "\nsCglib:\t" + tc + "\npCglib:\t" + tp + "\nhuTool:\t" + th + "\nmapStruct:\t" + tm);
}

輸出結(jié)果如下,滿足預(yù)期

source:	Source(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875)
apache:	Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875)
spring:	Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875)
sCglib:	Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875)
pCglib:	Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875)
huTool:	Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875)
mapStruct:	Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875)
6.2 性能測試

接下來我們關(guān)注一下不同的工具包,實現(xiàn)屬性拷貝的性能對比情況如何

public void test() throws Exception {
    // 第一次用于預(yù)熱
    autoCheck(Target2.class, 10000);
    autoCheck(Target2.class, 10000);
    autoCheck(Target2.class, 10000_0);
    autoCheck(Target2.class, 50000_0);
    autoCheck(Target2.class, 10000_00);
}

private <T> void autoCheck(Class<T> target, int size) throws Exception {
    StopWatch stopWatch = new StopWatch();
    runCopier(stopWatch, "apacheCopier", size, (s) -> apacheCopier.copy(s, target));
    runCopier(stopWatch, "springCglibCopier", size, (s) -> springCglibCopier.copy(s, target));
    runCopier(stopWatch, "pureCglibCopier", size, (s) -> pureCglibCopier.copy(s, target));
    runCopier(stopWatch, "hutoolCopier", size, (s) -> hutoolCopier.copy(s, target));
    runCopier(stopWatch, "springBeanCopier", size, (s) -> springBeanCopier.copy(s, target));
    runCopier(stopWatch, "mapStruct", size, (s) -> mapsCopier.copy(s, target));
    System.out.println((size / 10000) + "w -------- cost: " + stopWatch.prettyPrint());
}

private <T> void runCopier(StopWatch stopWatch, String key, int size, CopierFunc func) throws Exception {
    stopWatch.start(key);
    for (int i = 0; i < size; i++) {
        Source s = genSource();
        func.apply(s);
    }
    stopWatch.stop();
}

@FunctionalInterface
public interface CopierFunc<T> {
    T apply(Source s) throws Exception;
}

輸出結(jié)果如下

1w -------- cost: StopWatch '': running time = 583135900 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
488136600  084%  apacheCopier
009363500  002%  springCglibCopier
009385500  002%  pureCglibCopier
053982900  009%  hutoolCopier
016976500  003%  springBeanCopier
005290900  001%  mapStruct

10w -------- cost: StopWatch '': running time = 5607831900 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
4646282100  083%  apacheCopier
096047200  002%  springCglibCopier
093815600  002%  pureCglibCopier
548897800  010%  hutoolCopier
169937400  003%  springBeanCopier
052851800  001%  mapStruct

50w -------- cost: StopWatch '': running time = 27946743000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
23115325200  083%  apacheCopier
481878600  002%  springCglibCopier
475181600  002%  pureCglibCopier
2750257900  010%  hutoolCopier
855448400  003%  springBeanCopier
268651300  001%  mapStruct

100w -------- cost: StopWatch '': running time = 57141483600 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
46865332600  082%  apacheCopier
1019163600  002%  springCglibCopier
1033701100  002%  pureCglibCopier
5897726100  010%  hutoolCopier
1706155900  003%  springBeanCopier
619404300  001%  mapStruct
-1w10w50w100w
apache0.488136600s / 084%4.646282100s / 083%23.115325200s / 083%46.865332600s / 083%
spring cglib0.009363500s / 002%0.096047200s / 002%0.481878600s / 002%1.019163600s / 002%
pure cglibg0.009385500s / 002%0.093815600s / 002%0.475181600s / 002%1.033701100s / 002%
hutool0.053982900s / 009%0.548897800s / 010%2.750257900s / 010%5.897726100s / 010%
spring0.016976500s / 003%0.169937400s / 003%0.855448400s / 003%1.706155900s / 003%
mapstruct0.005290900s / 001%0.052851800s / 001%0.268651300s / 001%0.619404300s / 001%
total0.583135900s5.607831900s27.946743000s57.141483600s

上面的測試中,存在一個不同的變量,即不是用相同的source對象來測試不同的工具轉(zhuǎn)換情況,但是這個不同并不會太影響不同框架的性能對比,基本上從上面的運行結(jié)果來看

  • mapstruct, cglib, spring 表現(xiàn)最好

  • apache 表現(xiàn)最差

基本趨勢相當(dāng)于:

apache -> 10 * hutool -> 28 * spring -> 45 * cglib -> 83 * mapstruct

如果我們需要實現(xiàn)簡單的bean拷貝,選擇cglib或者spring的是個不錯選擇

    “常見Bean拷貝框架的性能有哪些區(qū)別”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI