关于java:常见Bean拷贝框架使用姿势及性能对比

7次阅读

共计 11962 个字符,预计需要花费 30 分钟才能阅读完成。

常见 Bean 拷贝框架应用姿态及性能比照

Bean 属性拷贝,次要针对几个罕用的拷贝框架进行性能比照,以及性能扩大反对

选用的框架

  • cglib (间接应用 Spring 封装的 BeanCopier)
  • apache
  • MapStruct
  • Spring
  • HuTool

<!– more –>

I. 背景

当业务量不大时,不论抉择哪个框架都没什么问题,只有性能反对就 ok 了;然而当数据量大的时候,可能就须要思考性能问题了;再理论的我的项目中,正好遇到了这个问题,不仅慢,还发现会有锁竞争,这特么就尼普了

我的项目中应用的是 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;
}

看下面的实现,每次获取 value 都加了一个同步锁,而且还是锁的全局的classCache,这就有些过分了啊,奥妙的是这段代码正文,谷歌翻译之后为

咱们不想在这里应用同步。对象援用是原子的,因而咱们能够只在启动时进行偶然的不必要查找。

这意思大略是说我就在启动的时候用一下,并不会频繁的应用,所以应用了同步代码块也问题不大 …

然而在 BeanUtils#copyProperties 中就蛋疼了,每次都会执行这个办法,扎心了


当然咱们当初个别用的 Spring5+ 了,这段代码也早就做了革新了,新版的如下,不再存在下面的这个并发问题了

/**
 * 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

阿里标准中,明确阐明了,不要应用它,idea 装置阿里的代码标准插件之后,会有提醒

应用姿态比较简单,引入依赖

<!-- 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();
        // 留神,第一个参数为 target,第二个参数为 source
        // 与其余的正好相同 
        BeanUtils.copyProperties(res, source);
        return res;
    }
}

2. cglib BeanCopier

cglib 是通过动静代理的形式来实现属性拷贝的,与下面基于反射实现形式存在实质上的区别,这也是它性能更优良的主因

在 Spring 环境下,个别不须要额定的引入依赖;或者间接引入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 对象转换
     *
     * @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;
    }
}

当然也能够间接应用污浊版的 cglib,引入依赖

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

应用姿态和下面截然不同

@Component
public class PureCglibCopier {
    /**
     * cglib 对象转换
     *
     * @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 来应用了,不然并发下的性能切实是感人

基于内省 + 反射,借助 getter/setter 办法实现属性拷贝,性能比 apache 高

外围依赖

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

属性拷贝

@Component
public class SpringBeanCopier {

    /**
     * 对象转换
     *
     * @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 会高一点,当低于 spring

引入依赖

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

应用姿态

@Component
public class HutoolCopier {

    /**
     * bean 对象转换
     *
     * @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 的转换接口,主动代码生成的形式来实现拷贝,性能媲美间接的 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);
    }
}

毛病也比拟显著,须要显示的接口转换申明

6. 测试

定义两个 Bean,用于转换测试,两个 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);
}

输入后果如下,满足预期

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 性能测试

接下来咱们关注一下不同的工具包,实现属性拷贝的性能比照状况如何

public void test() throws Exception {
    // 第一次用于预热
    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;
}

输入后果如下

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
1w 10w 50w 100w
apache 0.488136600s / 084% 4.646282100s / 083% 23.115325200s / 083% 46.865332600s / 083%
spring cglib 0.009363500s / 002% 0.096047200s / 002% 0.481878600s / 002% 1.019163600s / 002%
pure cglibg 0.009385500s / 002% 0.093815600s / 002% 0.475181600s / 002% 1.033701100s / 002%
hutool 0.053982900s / 009% 0.548897800s / 010% 2.750257900s / 010% 5.897726100s / 010%
spring 0.016976500s / 003% 0.169937400s / 003% 0.855448400s / 003% 1.706155900s / 003%
mapstruct 0.005290900s / 001% 0.052851800s / 001% 0.268651300s / 001% 0.619404300s / 001%
total 0.583135900s 5.607831900s 27.946743000s 57.141483600s

下面的测试中,存在一个不同的变量,即不是用雷同的 source 对象来测试不同的工具转换状况,然而这个不同并不会太影响不同框架的性能比照,基本上从下面的运行后果来看

  • mapstruct, cglib, spring 体现最好
  • apache 体现最差

根本趋势相当于:

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

如果咱们须要实现简略的 bean 拷贝,抉择 cglib 或者 spring 的是个不错抉择

III. 其余

1. 一灰灰 Blog:https://blog.hhui.top

一灰灰的集体博客,记录所有学习和工作中的博文,欢送大家前去逛逛

  • 我的项目源码: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/004-bean-util

2. 申明

尽信书则不如,以上内容,纯属一家之言,因集体能力无限,不免有疏漏和谬误之处,如发现 bug 或者有更好的倡议,欢送批评指正,不吝感谢

  • 微博地址: 小灰灰 Blog
  • QQ:一灰灰 /3302797840
  • 微信公众号: 一灰灰 blog

3. 扫描关注

一灰灰 blog

正文完
 0