千家信息网

常见Bean拷贝框架的性能有哪些区别

发表于:2025-02-03 作者:千家信息网编辑
千家信息网最后更新 2025年02月03日,本篇内容介绍了"常见Bean拷贝框架的性能有哪些区别"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!I
千家信息网最后更新 2025年02月03日常见Bean拷贝框架的性能有哪些区别

本篇内容介绍了"常见Bean拷贝框架的性能有哪些区别"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

I.背景

当业务量不大时,不管选择哪个框架都没什么问题,只要功能支持就ok了;但是当数据量大的时候,可能就需要考虑性能问题了;再实际的项目中,正好遇到了这个问题,不仅慢,还发现会有锁竞争,这特么就尼普了

项目中使用的是Spring的 BeanUtils, 版本 3.2.4.RELEASE, 版本相对较老,主要问题在于org.springframework.beans.CachedIntrospectionResults.forClass

/** * Create CachedIntrospectionResults for the given bean class. * 

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(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, 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安装阿里的代码规范插件之后,会有提示

使用姿势比较简单,引入依赖

    commons-beanutils    commons-beanutils    1.9.4

属性拷贝

@Componentpublic class ApacheCopier {    public  T copy(K source, Class target) throws IllegalAccessException, InstantiationException, InvocationTargetException {        T res = target.newInstance();        // 注意,第一个参数为target,第二个参数为source        // 与其他的正好相反         BeanUtils.copyProperties(res, source);        return res;    }}

2. cglib BeanCopier

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

在Spring环境下,一般不需要额外的引入依赖;或者直接引入spring-core

    org.springframework    spring-core    5.2.8.RELEASE    compile

属性拷贝

@Componentpublic class SpringCglibCopier {    /**     * cglib 对象转换     *     * @param source     * @param target     * @param      * @param      * @return     * @throws IllegalAccessException     * @throws InstantiationException     */    public  T copy(K source, Class target) throws IllegalAccessException, InstantiationException {        BeanCopier copier = BeanCopier.create(source.getClass(), target, false);        T res = target.newInstance();        copier.copy(source, res, null);        return res;    }}

当然也可以直接使用纯净版的cglib,引入依赖

    cglib    cglib    3.3.0

使用姿势和上面一模一样

@Componentpublic class PureCglibCopier {    /**     * cglib 对象转换     *     * @param source     * @param target     * @param      * @param      * @return     * @throws IllegalAccessException     * @throws InstantiationException     */    public  T copy(K source, Class 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高

核心依赖

    org.springframework    spring-beans    5.2.1.RELEASE    compile

属性拷贝

@Componentpublic class SpringBeanCopier {    /**     * 对象转换     *     * @param source     * @param target     * @param      * @param      * @return     * @throws IllegalAccessException     * @throws InstantiationException     */    public  T copy(K source, Class target) throws IllegalAccessException, InstantiationException {        T res = target.newInstance();        BeanUtils.copyProperties(source, res);        return res;    }}

4. hutool BeanUtil

hutool 提供了很多的java工具类,从测试效果来看它的性能比apache会高一点,当低于spring

引入依赖

    cn.hutool    hutool-core    5.6.0

使用姿势

@Componentpublic class HutoolCopier {    /**     * bean 对象转换     *     * @param source     * @param target     * @param      * @param      * @return     */    public  T copy(K source, Class target) throws Exception {        return BeanUtil.toBean(source, target);    }}

5. MapStruct

MapStruct 性能更强悍了,缺点也比较明显,需要声明bean的转换接口,自动代码生成的方式来实现拷贝,性能媲美直接的get/set

引入依赖

    org.mapstruct    mapstruct    1.4.2.Final    org.mapstruct    mapstruct-processor    1.4.2.Final

使用姿势

@Mapperpublic interface MapStructCopier {    Target copy(Source source);}@Componentpublic class MapsCopier {    private MapStructCopier mapStructCopier = Mappers.getMapper(MapStructCopier.class);    public Target copy(Source source, Class target) {        return mapStructCopier.copy(source);    }}

缺点也比较明显,需要显示的接口转换声明

6. 测试

定义两个Bean,用于转换测试,两个bean的成员属性名,类型完全一致

@Datapublic class Source {    private Integer id;    private String user_name;    private Double price;    private List ids;    private BigDecimal marketPrice;}@Datapublic class Target {    private Integer id;    private String user_name;    private Double price;    private List 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  void autoCheck(Class 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  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();}@FunctionalInterfacepublic interface CopierFunc {    T apply(Source s) throws Exception;}

输出结果如下

1w -------- cost: StopWatch '': running time = 583135900 ns---------------------------------------------ns         %     Task name---------------------------------------------488136600  084%  apacheCopier009363500  002%  springCglibCopier009385500  002%  pureCglibCopier053982900  009%  hutoolCopier016976500  003%  springBeanCopier005290900  001%  mapStruct10w -------- cost: StopWatch '': running time = 5607831900 ns---------------------------------------------ns         %     Task name---------------------------------------------4646282100  083%  apacheCopier096047200  002%  springCglibCopier093815600  002%  pureCglibCopier548897800  010%  hutoolCopier169937400  003%  springBeanCopier052851800  001%  mapStruct50w -------- cost: StopWatch '': running time = 27946743000 ns---------------------------------------------ns         %     Task name---------------------------------------------23115325200  083%  apacheCopier481878600  002%  springCglibCopier475181600  002%  pureCglibCopier2750257900  010%  hutoolCopier855448400  003%  springBeanCopier268651300  001%  mapStruct100w -------- cost: StopWatch '': running time = 57141483600 ns---------------------------------------------ns         %     Task name---------------------------------------------46865332600  082%  apacheCopier1019163600  002%  springCglibCopier1033701100  002%  pureCglibCopier5897726100  010%  hutoolCopier1706155900  003%  springBeanCopier619404300  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对象来测试不同的工具转换情况,但是这个不同并不会太影响不同框架的性能对比,基本上从上面的运行结果来看

  • mapstruct, cglib, spring 表现最好

  • apache 表现最差

基本趋势相当于:

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

如果我们需要实现简单的bean拷贝,选择cglib或者spring的是个不错选择

    "常见Bean拷贝框架的性能有哪些区别"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

    0