跟同事杠上了,Apache Beanutils为什么被禁止使用?
Posted 哪 吒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跟同事杠上了,Apache Beanutils为什么被禁止使用?相关的知识,希望对你有一定的参考价值。
收录于热门专栏Java基础教程系列(进阶篇)
在实际的项目开发中,对象间赋值普遍存在,随着双十一、秒杀等电商过程愈加复杂,数据量也在不断攀升,效率问题,浮出水面。
问:如果是你来写对象间赋值的代码,你会怎么做?
答:想都不用想,直接代码走起来,get、set即可。
问:下图这样?
答:对啊,你怎么能把我的代码放到网上?
问:没,我只是举个例子
答:这涉及到商业机密,是很严重的问题
问:我发现你挺能扯皮啊,直接回答问题行吗?
答:OK,OK,我也觉得这样写很low,上次这么写之后,差点挨打
- 对象太多,ctrl c + strl v,键盘差点没敲坏;
- 而且很容易出错,一不留神,属性没对应上,赋错值了;
- 代码看起来很傻缺,一个类好几千行,全是get、set复制,还起个了自以为很优雅的名字transfer;
- 如果属性名不能见名知意,还得加上每个属性的含义注释(基本这种赋值操作,都是要加的,注释很重要,注释很重要,注释很重要);
- 代码维护起来很麻烦;
- 如果对象过多,会产生类爆炸问题,如果属性过多,会严重违背阿里巴巴代码规约(一个方法的实际代码最多20行);
问:行了,行了,说说,怎么解决吧。
答:很简单啊,可以通过工具类Beanutils直接赋值啊
问:我听说工具类最近很卷,你用的哪个啊?
答:就Apache
自带的那个啊,贼简单。我手写一个,给你欣赏一下。
问:你这代码报错啊,避免用Apache Beanutils进行属性的copy。
答:没报错,只是严重警告而已,代码能跑就行,有问题再优化呗
问:你这什么态度?人事在哪划拉的人,为啥会出现严重警告?
答:拿多少钱,干多少活,我又不是XXX,应该是性能问题吧
问:具体什么原因导致的呢?
答:3000块钱还得手撕一下 apache copyProperties
的源代码呗?
通过单例模式调用copyProperties
,但是,每一个方法对应一个BeanUtilsBean.getInstance()
实例,每一个类实例对应一个实例,这不算一个真正的单例模式。
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException
BeanUtilsBean.getInstance().copyProperties(dest, orig);
性能瓶颈 --> 日志太多也是病
通过源码可以看到,每一个copyProperties
都要进行多次类型检查,还要打印日志。
/**
* org.apache.commons.beanutils.BeanUtils.copyProperties方法源码解析
* @author 哪吒编程
* @time 2023-01-07
*/
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException
// 类型检查
if (dest == null)
throw new IllegalArgumentException("No destination bean specified");
else if (orig == null)
throw new IllegalArgumentException("No origin bean specified");
else
// 打印日志
if (this.log.isDebugEnabled())
this.log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");
int var5;
int var6;
String name;
Object value;
// 类型检查
// DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能
if (orig instanceof DynaBean)
// 获取源对象所有属性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6)
DynaProperty origDescriptor = var4[var6];
// 获取源对象属性名
name = origDescriptor.getName();
// 判断源对象是否可读、判断目标对象是否可写
if (this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name))
// 获取对应的值
value = ((DynaBean)orig).get(name);
// 每个属性都调用一次copyProperty
this.copyProperty(dest, name, value);
else if (orig instanceof Map)
Map<String, Object> propMap = (Map)orig;
Iterator var13 = propMap.entrySet().iterator();
while(var13.hasNext())
Map.Entry<String, Object> entry = (Map.Entry)var13.next();
String name = (String)entry.getKey();
if (this.getPropertyUtils().isWriteable(dest, name))
this.copyProperty(dest, name, entry.getValue());
else
PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);
PropertyDescriptor[] var14 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6)
PropertyDescriptor origDescriptor = var14[var6];
name = origDescriptor.getName();
if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name))
try
value = this.getPropertyUtils().getSimpleProperty(orig, name);
this.copyProperty(dest, name, value);
catch (NoSuchMethodException var10)
通过 jvisualvm.exe 检测代码性能
再通过jvisualvm.exe
检测一下运行情况,果然,logging.log4j
赫然在列,稳居耗时Top1。
问:还有其它好的方式吗?性能好一点的
答:当然有,据我了解有 4 种工具类,实际上,可能会有更多,话不多说,先简单介绍一下。
- org.apache.commons.beanutils.BeanUtils;
- org.apache.commons.beanutils.PropertyUtils;
- org.springframework.cglib.beans.BeanCopier;
- org.springframework.beans.BeanUtils;
问:那你怎么不用?
答:OK,我来演示一下
package com.nezha.copy;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.StopWatch;
public class Test
public static void main(String[] args)
User user = new User();
user.setUserId("1");
user.setUserName("哪吒编程");
user.setCardId("123");
user.setCreateTime("2023-01-03");
user.setEmail("666666666@qq.com");
user.setOperate("哪吒");
user.setOrgId("46987916");
user.setPassword("123456");
user.setPhone("10086");
user.setRemark("456");
user.setSex(1);
user.setStatus("1");
user.setTel("110");
user.setType("0");
user.setUpdateTime("2023-01-05");
User target = new User();
int sum = 10000000;
apacheBeanUtilsCopyTest(user,target,sum);
commonsPropertyCopyTest(user,target,sum);
cglibBeanCopyTest(user,target,sum);
springBeanCopyTest(user,target,sum);
private static void apacheBeanUtilsCopyTest(User source, User target, int sum)
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++)
apacheBeanUtilsCopy(source,target);
stopWatch.stop();
System.out.println("使用org.apache.commons.beanutils.BeanUtils方式赋值"+sum+"个user对象,耗时:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
/**
* org.apache.commons.beanutils.BeanUtils方式
*/
private static void apacheBeanUtilsCopy(User source, User target)
try
BeanUtils.copyProperties(source, target);
catch (Exception e)
private static void commonsPropertyCopyTest(User source, User target, int sum)
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++)
commonsPropertyCopy(source,target);
stopWatch.stop();
System.out.println("使用org.apache.commons.beanutils.PropertyUtils方式赋值"+sum+"个user对象,耗时:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
/**
* org.apache.commons.beanutils.PropertyUtils方式
*/
private static void commonsPropertyCopy(User source, User target)
try
PropertyUtils.copyProperties(target, source);
catch (Exception e)
private static void cglibBeanCopyTest(User source, User target, int sum)
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++)
cglibBeanCopy(source,target);
stopWatch.stop();
System.out.println("使用org.springframework.cglib.beans.BeanCopier方式赋值"+sum+"个user对象,耗时:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
/**
* org.springframework.cglib.beans.BeanCopier方式
*/
static BeanCopier copier = BeanCopier.create(User.class, User.class, false);
private static void cglibBeanCopy(User source, User target)
copier.copy(source, target, null);
private static void springBeanCopyTest(User source, User target, int sum)
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++)
springBeanCopy(source,target);
stopWatch.stop();
System.out.println("使用org.springframework.beans.BeanUtils.copyProperties方式赋值"+sum+"个user对象,耗时:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
/**
* org.springframework.beans.BeanUtils.copyProperties方式
*/
private static void springBeanCopy(User source, User target)
org.springframework.beans.BeanUtils.copyProperties(source, target);
“四大金刚” 性能统计
方法 | 1000 | 10000 | 100000 | 1000000 |
---|---|---|---|---|
apache BeanUtils | 906毫秒 | 807毫秒 | 1892毫秒 | 11049毫秒 |
apache PropertyUtils | 17毫秒 | 96毫秒 | 648毫秒 | 5896毫秒 |
spring cglib BeanCopier | 0毫秒 | 1毫秒 | 3毫秒 | 10毫秒 |
spring copyProperties | 87毫秒 | 90毫秒 | 123毫秒 | 482毫秒 |
不测不知道,一测吓一跳,差的还真的多。
spring cglib BeanCopier
性能最好,apache BeanUtils
性能最差。
性能走势 --> spring cglib BeanCopier
优于 spring copyProperties
优于 apache PropertyUtils
优于 apache BeanUtils
避免用Apache Beanutils进行属性的copy的问题 上面分析完了,下面再看看其它的方法做了哪些优化。
Apache PropertyUtils 源码分析
从源码可以清晰的看到,类型检查变成了非空校验,去掉了每一次copy的日志记录,性能肯定更好了。
- 类型检查变成了非空校验
- 去掉了每一次copy的日志记录
- 实际赋值的地方由copyProperty变成了DanyBean + setSimpleProperty;
DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能。
/**
* org.apache.commons.beanutils.PropertyUtils方式源码解析
* @author 哪吒编程
* @time 2023-01-07
*/
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
// 判断数据源和目标对象不是null
if (dest == null)
throw new IllegalArgumentException("No destination bean specified");
else if (orig == null)
throw new IllegalArgumentException("No origin bean specified");
else
// 删除了org.apache.commons.beanutils.BeanUtils.copyProperties中最为耗时的log日志记录
int var5;
int var6;
String name;
Object value;
// 类型检查
if (orig instanceof DynaBean)
// 获取源对象所有属性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6)
DynaProperty origDescriptor = var4[var6];
// 获取源对象属性名
name = origDescriptor.getName();
// 判断源对象是否可读、判断目标对象是否可写
if (this.isReadable(orig, name) && this.isWriteable(dest, name))
try
// 获取对应的值
value = ((DynaBean)orig).get(name);
// 相对于org.apache.commons.beanutils.BeanUtils.copyProperties此处有优化
// DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能
if (dest instanceof DynaBean)
((DynaBean)dest).set(name, value);
else
// 每个属性都调用一次copyProperty
this.setSimpleProperty(dest, name, value);
catch (NoSuchMethodException var12)
if (this.log.isDebugEnabled())
this.log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", var12);
else if (orig instanceof Map)
Iterator entries = ((Map)orig).entrySet().iterator();
while(true)
Map.Entry entry;
String name;
do
if (!entries.hasNext())
return;
entry = (Map.Entry)entries.next();
name = (String)entry.getKey();
不瞒你说,我和 Spring 源码杠上了