Java Bean Copy框架性能对比

本文涉及的产品
轻量应用服务器 2vCPU 4GiB,适用于网站搭建
轻量应用服务器 2vCPU 4GiB,适用于搭建Web应用/小程序
轻量应用服务器 2vCPU 4GiB,适用于搭建容器环境
简介: 一、问题分析 背景 相同server机器上的相同方法在方法调用链任何参数都一致的情况消耗时间差别非常大,举例说明,类A有方法demo(), 通过分析发现同一台机器(也是一个jvm进程)对该方法的两次调用消耗时间竟然有200ms的差距。

一、问题分析

背景

相同server机器上的相同方法在方法调用链任何参数都一致的情况消耗时间差别非常大,举例说明,类A有方法demo(), 通过分析发现同一台机器(也是一个jvm进程)对该方法的两次调用消耗时间竟然有200ms的差距。
同时,方法实现上没有使用任何的并发以及缓存,唯一特殊的是方法内使用了Apache BeanUtils.copyProperties,怀疑是这个方法有猫腻,于是开始重点分析该方法实现。

分析过程

现象分析

猜想如果是BeanUtils.copyProperties有问题,那么现象上应该是调用BeanUtils.copyProperties完成bean copy的过程可能会偶然出现性能问题,于是写了一个demo 循环调用BeanUtils.copyProperties完成bean copy,demo可以参考下文

验证结果:

* 单线程模型下,第一次访问BeanUtils.copyProperties耗时有200-300ms左右,后续访问几乎都是0ms,也就是微秒级别
* 并发模型下,每个线程访问BeanUtils.copyProperties会有一次200-300ms耗时, 也就是高性能耗时次数与并发线程数一致

根据以上验证结果猜测:

* BeanUtils.copyProperties有一种线程级别的“缓存”,第一次刷新缓存耗时较长,后续直接读”缓存”耗时较短
* 这种“缓存”是线程粒度
源码剖析

首先,要获取一个BeanUtilsBean实例,猜测这应该是一个单例模型的实现
image

接下来我们看其实现,原来是一个“假单例”,并且是一个线程对应一个BeanUtils实例,接着看下去
image

实现上为了保证线程安全使用了synchronized ,随后debug了一下性能耗时主要在initalvalue(),可以看到线程内只有第一次调用get会初始化执行该方法,那么理论上是说得通了。
image

通过分析源码很容易解释为啥同一个方法调用会偶然耗时较长了,主要两个原因:

  1. 两个方法在不同线程执行,如果其中一个线程是第一次调用,框架需要先初始化BeanUtils实例,需要消耗200ms左右的性能
  2. 并发访问同一个BeanUtils实例时会出现线程阻塞

二 、Apache, Cglib, Spring bean copy 性能对比

目前主流的bean copy框架有apache, cglib, springframework 等,写法上大同小异,作为开发者我们更关注的偏向于性能,下文demo将综合对比apache,cglib,springframework以及传统的java bean 属性set等四种方案的性能指标。

2.1 代码结构介绍

首先,定义一个通用接口,包含一个方法copyBean

package com.free.life.base.beancopy;

/**
 * bean copy common facade.
 *
 * @author yzq
 * @date 2018/01/16
 */
public interface BeanCopyFacade<S, T> {

    /**
     * bean copy.
     *
     * @param sourceBean source bean
     * @param targetBean target bean
     * @throws Exception root exception
     */
    void copyBean(S sourceBean, T targetBean) throws Exception;
}

使用apache BeanUtils实现方式

package com.free.life.base.beancopy;

import org.apache.commons.beanutils.BeanUtils;

/**
 * apache copyProperties.
 *
 * @author yzq
 * @date 2018/01/16
 */
public class ApacheBeanCopy implements BeanCopyFacade<SourceBean, TargetBean> {

    @Override
    public void copyBean(SourceBean sourceBean, TargetBean targetBean) throws Exception {
        long start = System.nanoTime();
        BeanUtils.copyProperties(targetBean, sourceBean);
        long end = System.nanoTime();

        System.out.println(String.format("%s consume %d microsecond", "apache  copy property", (end - start) / 1000));
    }
}

使用cglib BeanCopier实现

package com.free.life.base.beancopy;

import net.sf.cglib.beans.BeanCopier;

/**
 * cglib BeanCopier copy.
 *
 * @author yzq
 * @date 2018/01/16
 */
public class CglibBeanCopy implements BeanCopyFacade<SourceBean, TargetBean> {

    private BeanCopier beanCopier = BeanCopier.create(SourceBean.class, TargetBean.class, false);

    @Override
    public void copyBean(SourceBean sourceBean, TargetBean targetBean) throws Exception {
        long start = System.nanoTime();
        beanCopier.copy(sourceBean, targetBean, null);
        long end = System.nanoTime();

        System.out.println(String.format("%s consume %d microsecond", "cglib BeanCopier", (end - start) / 1000));
    }
}

使用spring BeanUtils

package com.free.life.base.beancopy;

import org.springframework.beans.BeanUtils;

/**
 * spring framework copy bean.
 *
 * @author yzq
 * @date 2018/01/16
 */
public class SpringBeanCopy implements BeanCopyFacade<SourceBean, TargetBean> {

    @Override
    public void copyBean(SourceBean sourceBean, TargetBean targetBean) throws Exception {
        long start = System.nanoTime();
        BeanUtils.copyProperties(sourceBean, targetBean);
        long end = System.nanoTime();

        System.out.println(String.format("%s consume %d microsecond", "spring copyProperties", (end - start) / 1000));
    }
}

使用 java setter

package com.free.life.base.beancopy;

/**
 * use setter/getter
 *
 * @author yzq
 * @date 2018/01/16
 */
public class JavaBeanCopy implements BeanCopyFacade<SourceBean, TargetBean> {

    @Override
    public void copyBean(SourceBean sourceBean, TargetBean targetBean) throws Exception {
        long start = System.nanoTime();
        targetBean.setId(sourceBean.getId());
        targetBean.setName(sourceBean.getName());
        targetBean.setResult(sourceBean.getResult());
        targetBean.setContent(sourceBean.getContent());
        long end = System.nanoTime();

        System.out.println(String.format("%s consume %d microsecond", "use setter", (end - start) / 1000));
    }
}

Main方法入口测试多种方案性能

package com.free.life.base.beancopy;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * bean copy demo.
 *
 * @author yzq
 * @date 2018/01/16
 */
public class BeanCopyDemo {

    private static BeanCopyFacade apacheBeanCopy;
    private static BeanCopyFacade cglibBeanCopy;
    private static BeanCopyFacade springBeanCopy;
    private static BeanCopyFacade javaBeanCopy;

    static {
        apacheBeanCopy = new ApacheBeanCopy();
        cglibBeanCopy = new CglibBeanCopy();
        springBeanCopy = new SpringBeanCopy();
        javaBeanCopy = new JavaBeanCopy();
    }

    public static void main(String[] args) throws Exception {
        final Integer loopCount = 10;

        SourceBean sourceBean = new SourceBean();
        sourceBean.setId(1);
        sourceBean.setName("yzq");
        sourceBean.setResult(Boolean.TRUE);
        sourceBean.setContent("bean copy test.");

        TargetBean targetBean = new TargetBean();

        multiThread(loopCount, sourceBean, targetBean);

        singleThreadTest(loopCount, sourceBean, targetBean);
    }

    private static void multiThread(Integer loopCount, SourceBean sourceBean, TargetBean targetBean) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < loopCount; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        apacheBeanCopy.copyBean(sourceBean, targetBean);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    private static void singleThreadTest(Integer loopCount, SourceBean sourceBean, TargetBean targetBean)
        throws Exception {
        System.out.println("---------------- apache ----------------------");

        for (int i = 0; i < loopCount; i++) {

            apacheBeanCopy.copyBean(sourceBean, targetBean);
        }

        System.out.println("---------------- cglib ----------------------");

        for (int i = 0; i < loopCount; i++) {
            cglibBeanCopy.copyBean(sourceBean, targetBean);
        }

        System.out.println("----------------- spring ---------------------");

        for (int i = 0; i < loopCount; i++) {
            springBeanCopy.copyBean(sourceBean, targetBean);
        }

        System.out.println("----------------- setter ---------------------");

        for (int i = 0; i < loopCount; i++) {
            javaBeanCopy.copyBean(sourceBean, targetBean);
        }
    }

}

2.2 测试结果

运行环境:
* macbook pro i7,4core,16g
* jdk:1.8.0_144
测试方式:
* 循环1w次调用
测试结果均值(单位为微秒):
* apache:  200
* cglib: 1
* spring: 20
* setter:  0

综上: 性能 setter > cglib > spring > apache

三 、最佳实践

bean copy对比传统的做法优缺点

优点

* 写法优雅简洁
* 一些相对高阶的使用方式比较简洁,比如反射方式获取类属性值等

缺点

* 性能较差,因为beancopy框架背后的实现都是通过[java反射](https://docs.oracle.com/javase/tutorial/reflect/index.html)机制去做的,通常情况性能不会比normal方式更优。
* 引用查找难,bean copy的实现会隐藏对象属性的设置的调用,比如copy(source,taget) 我想查看target属性A有哪些地方被设值了,那么通过IDE查找引用会漏掉bean copy的引用。

实践建议

  • bean copy场景较少或者对性能要求较高的部分避免使用任何bean copy框架
  • 如果要使用bean copy框架,优先使用cglib,且要做性能测试。同时需要注意:cglib使用BeanCopier.create()也是非常耗时,避免多次调用,尽可能做成全局初始化一次
  • 可以使用lombok builder或者自己封装builder模式相对优雅的代替setter/bean copy
  • 关注IDE的任何一个警告信息,尽可能消除任何警告信息

一点感想

  • 代码没有秘密,代码是可信的,同时它也是不可信的!
  • 充分利用工具解决问题
目录
相关文章
|
4月前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
77 3
|
7天前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
73 14
|
1月前
|
机器学习/深度学习 人工智能 Java
Java机器学习实战:基于DJL框架的手写数字识别全解析
在人工智能蓬勃发展的今天,Python凭借丰富的生态库(如TensorFlow、PyTorch)成为AI开发的首选语言。但Java作为企业级应用的基石,其在生产环境部署、性能优化和工程化方面的优势不容忽视。DJL(Deep Java Library)的出现完美填补了Java在深度学习领域的空白,它提供了一套统一的API,允许开发者无缝对接主流深度学习框架,将AI模型高效部署到Java生态中。本文将通过手写数字识别的完整流程,深入解析DJL框架的核心机制与应用实践。
73 3
|
11天前
|
存储 安全 Java
Java 集合框架详解:系统化分析与高级应用
本文深入解析Java集合框架,涵盖List、Set、Map等核心接口及其常见实现类,如ArrayList、HashSet、HashMap等。通过对比不同集合类型的特性与应用场景,帮助开发者选择最优方案。同时介绍Iterator迭代机制、Collections工具类及Stream API等高级功能,提升代码效率与可维护性。适合初学者与进阶开发者系统学习与实践。
38 0
|
2月前
|
存储 缓存 Java
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
242 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
|
1月前
|
存储 并行计算 Java
java 中的fork join框架
Java中的Fork Join框架于Java 7引入,旨在提升并行计算能力。它通过“分而治之”的思想,将大任务拆分为多个小任务(fork),再将结果合并(join)。核心组件包括:ForkJoinPool(管理线程池和工作窃取机制)、ForkJoinWorkerThread(执行具体任务的工作线程)和ForkJoinTask(定义任务逻辑,常用子类为RecursiveAction和RecursiveTask)。框架支持通过invoke、fork/join等方式提交任务,广泛应用于高性能并发场景。
|
1月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
3月前
|
并行计算 算法 Java
Java中的Fork/Join框架详解
Fork/Join框架是Java并行计算的强大工具,尤其适用于需要将任务分解为子任务的场景。通过正确使用Fork/Join框架,可以显著提升应用程序的性能和响应速度。在实际应用中,应结合具体需求选择合适的任务拆分策略,以最大化并行计算的效率。
85 23
|
2月前
|
人工智能 Java 数据处理
Java高级应用开发:基于AI的微服务架构优化与性能调优
在现代企业级应用开发中,微服务架构虽带来灵活性和可扩展性,但也增加了系统复杂性和性能瓶颈。本文探讨如何利用AI技术,特别是像DeepSeek这样的智能工具,优化Java微服务架构。AI通过智能分析系统运行数据,自动识别并解决性能瓶颈,优化服务拆分、通信方式及资源管理,实现高效性能调优,助力开发者设计更合理的微服务架构,迎接未来智能化开发的新时代。
|
5月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
105 4