一、问题分析

背景

相同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实例,猜测这应该是一个单例模型的实现

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

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

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

两个方法在不同线程执行,如果其中一个线程是第一次调用,框架需要先初始化BeanUtils实例,需要消耗200ms左右的性能

并发访问同一个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{

/**

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

@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{

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{

@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{

@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的任何一个警告信息,尽可能消除任何警告信息

一点感想

代码没有秘密,代码是可信的,同时它也是不可信的!

充分利用工具解决问题

java bean 优缺点_Java Bean Copy框架性能对比相关推荐

  1. Java kryo/protobuf/protostuff序列化 or Json 性能对比

    Java kryo/protobuf/protostuff序列化 or Json 性能对比 - 腾飞的鹰 - 博客园 对于一个java object的序列化,想测一下使用json和使用一般序列化工具, ...

  2. Java常用消息队列原理介绍及性能对比

    消息队列使用场景 为什么会需要消息队列(MQ)? 解耦  在项目启动之初来预测将来项目会碰到什么需求,是极其困难的.消息系统在处理过程中间插入了一个隐含的.基于数据的接口层,两边的处理过程都要实现这一 ...

  3. 几种流行Webservice框架性能对比

    1      摘要 开发webservice应用程序中离不开框架的支持,当open-open网站列举的就有30多种,这对于开发者如何选择带来一定的疑惑.性能Webservice的关键要素,不同的框架性 ...

  4. Bean复制的几种框架性能比较(BeanUtils、PropertyUtils、BeanCopier)

    作者:费晓晖 cnblogs.com/kaka/archive/2013/03/06/2945514.html 作为一个新员工,一个首要的工作就是阅读别人的代码,阅读代码的诸多好处就不说了,我就直奔主 ...

  5. java 网站计数器_Java Bean实现网页来访计数器

    1.JSP页: Document   : index Created on : 2009-10-10, 14:47:46 Author     : lucifer --%> /p> &qu ...

  6. java bean 验证_Java Bean验证基础

    java bean 验证 这篇文章总结了一些简单,快速的示例,这些示例说明了您想使用Java Beans Validation API(JSR 349,JSR 303)进行的最常见操作. 记住,Bea ...

  7. java bean 序列化_JAVA bean为何要实现序列化

    简而言之:序列化,就是为了在不同时间或不同平台的JVM之间共享实例对象.即序列化出于两个原因:①.用于持久化到磁盘上:②.用于作为数据流在网络上传输. 所谓的Serializable,就是java提供 ...

  8. java bean生成_Java Bean 生成器

    这是一个java bean生成器,利用一个文本文件(不妨称为bean定义文件)生成java bean. 开发过程中我们可能会用到很多数据对象(值对象),大都被封装成一个bean.虽然各大 IDE都提供 ...

  9. java protostuff 好处_Java 序列化框架性能对比(kryo、hessian、java、protostuff)

    简介: 优点 缺点 Kryo 速度快,序列化后体积小 跨语言支持较复杂 Hessian 默认支持跨语言 较慢 Protostuff 速度快,基于protobuf 需静态编译 Protostuff-Ru ...

最新文章

  1. android -各种适配器
  2. [轉]在jQuery1.5中使用deferred对象 - 拿着放大镜看Promise
  3. 这41条科研经验,让你少走很多弯路!
  4. 调试内存_C/C++程序调试和内存检测
  5. CSS3 transform
  6. Java基础篇:equals()方法与==的区别
  7. animateWithDuration
  8. C#中利用委托实现多线程跨线程操作
  9. linux nginx http cache时间不对,Linux中Nginx设置proxy_cache缓存与取消缓存-linux-操作系统-壹...
  10. Vue中的三种Watcher
  11. 搭建JAVA Eclipse环境并创建JAVA第一个项目
  12. 苹果耳罩式耳机曝光 将于今年下半年发布
  13. to load JavaHL Library解决方法
  14. bbs小项目整理(六)(消息发布)
  15. Edit Control最简单使用
  16. Qt定时器QTimer使用教程与代码演示
  17. ASP使用ASPupload组件上传多个文件
  18. “洋葱狗”潜伏3年终曝光,定期偷袭能源及交通行业
  19. 帝国插件自动采集文章主动推送给搜索引擎自动安装目录
  20. 网易邮箱客户端服务器设置

热门文章

  1. 用友U8如何调整凭证打印模板
  2. 职业教育的春天 ——职业教育系列行研报告(一)
  3. 硅谷互联网巨头们对区块链PlusFo超级链感兴趣?原因何在!
  4. statistical thinking in Python EDA
  5. 用Vue实现腾讯新闻页面
  6. 乐教乐学各关的解(51-60)
  7. 徐小湛概率论与数理统计课件_概率论与数理统计-徐小湛-视频教程70讲
  8. 中年危机如何化解分析
  9. win10安装redis及redis客户端使用方法
  10. java使用Stream流找出集合对象中最小值